Introduction

This vignette illustrates the use of INLA for spatial prediction using examples from Blangiardo and Cameletti (2015) and a classic point process dataset. For prediction of continuous spatial processes, the Lindgren, Rue, and Lindström (2011) stochastic partial differential equations (SPDE) approach is used to approximate the process through an areal Gaussian Markov random field (GMRF) representation. Finally, Log-Gaussian Cox process models are fit using the pseudodata approach of Simpson et al. (2016).

GMRF Background

Blangiardo and Cameletti (2015) section 6.1.

SPDE Background

Geostatistics Example

Toy dataset from Blangiardo and Cameletti (2015). A spatial process is observed via a random sampling plan that concentrates observations in the lower left of the unit square.

# Plot the data.
plot(s2 ~ s1, col = rgb(SPDEtoy$y / max(SPDEtoy$y), 0, 0), data = SPDEtoy, pch = 19, asp = 1, main = 'Toy Data')

# Create a mesh for the SPDE method and then plot it.
toy_mesh <- inla.mesh.2d(as.matrix(SPDEtoy[,c('s1', 's2')]), max.edge = c(0.1, 0.2))
plot(toy_mesh, asp = 1)
points(SPDEtoy$s1, SPDEtoy$s2, col = rgb(SPDEtoy$y / max(SPDEtoy$y), 0, 0, 0.5), pch = 20)

# SPDE projector matrix for estimation.
A_est <- inla.spde.make.A(toy_mesh, as.matrix(SPDEtoy[,c('s1', 's2')]))

# Initialize exponential covariance structure for SPDE.
spde <- inla.spde2.matern(mesh = toy_mesh, alpha = 2)

# Set up stack for estimation.
stack_index <- inla.spde.make.index(name = 'spatial_field', n.spde = spde$n.spde)
stack_est <- inla.stack(data = list(y = SPDEtoy$y), A = list(A_est), effects = list(c(stack_index, list(intercept = 1))), tag = 'est')

# Create a grid for prediction.
toy_nx <- 50
toy_ny <- 50
toy_grid <- expand.grid(x = seq(0, 1, length.out = toy_nx), y = seq(0, 1, length.out = toy_ny))

# SPDE projector matrix for prediction.
A_pred <- inla.spde.make.A(mesh = toy_mesh, loc = as.matrix(toy_grid))

# Set up stacks for prediction.
stack_latent <- inla.stack(data = list(xi = NA), A = list(A_pred), effects = list(stack_index), tag = 'pred_latent')
stack_response <- inla.stack(data = list(y = NA), A = list(A_pred), effects = list(c(stack_index, list(intercept = 1))), tag = 'pred_response')

# Join all three stacks.
stacks <- inla.stack(stack_est, stack_latent, stack_response)

# Fit the model with INLA.
toy_fit <- inla(
  y ~ -1 + intercept + f(spatial_field, model = spde),
  data = inla.stack.data(stacks),
  control.predictor = list(A = inla.stack.A(stacks), compute = TRUE)
)

# Output posterior summaries.
toy_fit$summary.fixed
toy_fit$summary.hyperpar
# Extract posterior mean of latent spatial field.
index_latent <- inla.stack.index(stacks, tag = 'pred_latent')$data
post_mean <- toy_fit$summary.linear.predictor[index_latent, 'mean']
post_sd <- toy_fit$summary.linear.predictor[index_latent, 'sd']

# Plot the posterior mean and SD of the latent spatial field.
plot(im(matrix(post_mean, nrow = toy_nx, ncol = toy_ny), xrange = range(toy_grid$x), yrange = range(toy_grid$y)), main = 'Posterior Mean of Spatial Field')
points(SPDEtoy$s1, SPDEtoy$s2, col = rgb(SPDEtoy$y / max(SPDEtoy$y), 0, 0, 0.5), pch = 20)

plot(im(matrix(post_sd, nrow = toy_nx, ncol = toy_ny), xrange = range(toy_grid$x), yrange = range(toy_grid$y)), main = 'Posterior SD of Spatial Field')
points(SPDEtoy$s1, SPDEtoy$s2, col = rgb(SPDEtoy$y / max(SPDEtoy$y), 0, 0, 0.5), pch = 20)

Bei Dataset

Example from Møller and Waagepetersen (2007), Beilschmiedia pendula Lauraceae locations in a plot in Panama. bei dataset in spatstat (Baddeley and Turner 2005). The illustrations below fit a stationary LGCP model with no covariates, a Matèrn covariance function with \(\nu = 1\), and INLA’s default (flat?) priors for all parameters.

# Plot the full point pattern.
plot(bei, pch = '.', cols = 'black', main = 'Realized Point Pattern')

# Take a sample of quadrats and plot the observed point pattern.
set.seed(84323)
N_QUADS <- 10
QUAD_SIZE <- 50

w_edge <- Frame(bei)$xrange[1]
e_edge <- Frame(bei)$xrange[2]
s_edge <- Frame(bei)$yrange[1]
n_edge <- Frame(bei)$yrange[2]

botleft <- cbind(
  runif(N_QUADS, w_edge, e_edge - QUAD_SIZE),
  runif(N_QUADS, s_edge, n_edge - QUAD_SIZE)
)
bei_interior <- lapply(seq_len(nrow(botleft)), function(r){return(
    cbind(
      botleft[r, 1] + c(0, 0, QUAD_SIZE, QUAD_SIZE),
      botleft[r, 2] + c(0, QUAD_SIZE, QUAD_SIZE, 0)
    )
  )})
bei_win <- do.call(
  union.owin,
  apply(botleft, 1, function(x){return(
    owin(x[1] + c(0, QUAD_SIZE), x[2] + c(0, QUAD_SIZE))
  )})
)
bei_hole <- bei[complement.owin(bei_win, frame = Frame(bei))]
bei_samp <- bei[bei_win]
bei_window_full <- Window(bei)

plot(bei_hole, main = 'Observed Region with Holes', pch = '.', cols = 'black')

plot(bei_window_full, main = 'Observed Sample')
plot(bei_win, add = TRUE)
plot(bei_samp, pch = '.', cols = 'black', add = TRUE)

Triangulation Meshes for SPDE Approach

The mesh should be fine in observed regions to accurately represent complex local structure but can be coarsened in unobserved regions where the model cannot infer as much detail. I am including margins to explore model behavior away from the observed regions.

# Parameters to experiment with.
MAX_EDGE_LENGTH <- 25
MAX_EDGE_EXT <- 50
MARGIN <- 100

# Mesh covering the site.
bei_boundary <- inla.mesh.segment(loc = do.call(cbind, vertices.owin(Window(bei))))
bei_full_mesh <- inla.mesh.create(
  boundary = bei_boundary,
  refine = list(max.edge = MAX_EDGE_LENGTH)
)
bei_full_spde <- inla.spde2.matern(mesh = bei_full_mesh)
plot(bei_full_mesh, asp = 1, main = 'Fine Mesh')
points(bei, pch = 19, cex = 0.25, col = 'red')
title('Fine Mesh')

# Mesh including a margin outside the site.
margin_mesh <- inla.mesh.2d(
  loc = bei_full_mesh$loc[,1:2], # Include nodes from site.
  offset = MARGIN,
  max.edge = MAX_EDGE_EXT # Fill in the rest with a coarser triangulation.
)
margin_spde <- inla.spde2.matern(mesh = margin_mesh)
plot(margin_mesh, asp = 1, main = 'Fine Mesh with Coarse Margin')
points(bei, pch = 19, cex = 0.25, col = 'red')
title('Fine Mesh with Coarse Margin')

# Meshs with coarser resolution in quadrats.
quad_hole <- do.call(
  inla.mesh.segment,
  lapply(seq_along(bei_interior), function(i){
    return(inla.mesh.segment(loc = bei_interior[[i]], grp = i - 1))
  })
)
bei_hole_mesh0 <- inla.mesh.create(
  boundary = list(bei_boundary, quad_hole),
  refine = list(max.edge = MAX_EDGE_LENGTH)
)
bei_hole0_spde <- inla.spde2.matern(mesh = bei_hole_mesh0)
plot(bei_hole_mesh0, asp = 1, main = 'Fine Mesh with Holes')
points(bei_hole, pch = 19, cex = 0.25, col = 'red')
title('Fine Mesh with Holes')

bei_hole_mesh <- inla.mesh.create(
  loc = bei_hole_mesh0$loc[,1:2], # Include nodes from mesh with holes.
  boundary = bei_boundary,
  refine = list(max.edge = MAX_EDGE_EXT) # Fill in the rest with a coarser triangulation.
)
bei_hole_spde <- inla.spde2.matern(mesh = bei_hole_mesh)
plot(bei_hole_mesh, asp = 1, main = 'Fine Mesh with Coarse Holes')
points(bei_hole, pch = 19, cex = 0.25, col = 'red')
title('Fine Mesh with Coarse Holes')

# Meshs with finer resolution in quadrats.
quad_bnd <- do.call(
  inla.mesh.segment,
  lapply(seq_along(bei_interior), function(i){
    return(inla.mesh.segment(loc = apply(bei_interior[[i]], 2, rev), grp = i - 1))
  })
)
bei_samp_mesh0 <- inla.mesh.create(
  boundary = quad_bnd,
  refine = list(max.edge = MAX_EDGE_LENGTH)
)
bei_samp0_spde <- inla.spde2.matern(mesh = bei_samp_mesh0)
plot(bei_samp_mesh0, asp = 1, main = 'Fine Mesh in Quadrats')
points(bei_samp, pch = 19, cex = 0.25, col = 'red')
title('Fine Mesh in Quadrats')

bei_samp_mesh <- inla.mesh.create(
  loc = bei_samp_mesh0$loc[,1:2], # Include nodes from mesh in quads.
  boundary = bei_boundary,
  refine = list(max.edge = MAX_EDGE_EXT) # Fill in the rest with a coarser triangulation.
)
bei_samp_spde <- inla.spde2.matern(mesh = bei_samp_mesh)
plot(bei_samp_mesh, asp = 1, main = 'Coarse Mesh with Fine Quadrats')
points(bei_samp, pch = 19, cex = 0.25, col = 'red')
title('Coarse Mesh with Fine Quadrats')

# Meshes with varying resolution in quadrats and a margin.
margin_hole <- inla.mesh.2d(
  loc = bei_hole_mesh$loc[,1:2], # Include nodes from mesh with holes.
  offset = MARGIN,
  max.edge = MAX_EDGE_EXT # Fill in the rest with a coarser triangulation.
)
margin_hole_spde <- inla.spde2.matern(mesh = margin_hole)
plot(margin_hole, asp = 1, main = 'Coarse Holes and Margin')
points(bei_hole, pch = 19, cex = 0.25, col = 'red')
title('Coarse Holes and Margin')

margin_samp <- inla.mesh.2d(
  loc = bei_samp_mesh$loc[,1:2], # Include nodes from quads.
  offset = MARGIN,
  max.edge = MAX_EDGE_EXT # Fill in the rest with a coarser triangulation.
)
margin_samp_spde <- inla.spde2.matern(mesh = margin_samp)
plot(margin_samp, asp = 1, main = 'Fine Quadrats and Coarse Margin')
points(bei_samp, pch = 19, cex = 0.25, col = 'red')
title('Fine Quadrats and Coarse Margin')

# Identify which mesh nodes are in the oberserved region
# and create SPDE projectors for spatial mapping.
NPIX_X <- 400
NPIX_Y <- 200

obs_full <- rep(0, margin_mesh$n)
obs_full[inla.over_sp_mesh(as(Window(bei), 'SpatialPolygons'), margin_mesh, 'vertex')] <- 1
proj_margin_mesh <- inla.mesh.projector(margin_mesh, dims = c(NPIX_X, NPIX_Y))

obs_hole <- rep(0, margin_hole$n)
obs_hole[inla.over_sp_mesh(as(Window(bei_hole), 'SpatialPolygons'), margin_hole, 'vertex')] <- 1
proj_margin_hole <- inla.mesh.projector(margin_hole, dims = c(NPIX_X, NPIX_Y))

obs_samp <- rep(0, margin_samp$n)
obs_samp[inla.over_sp_mesh(as(Window(bei_samp), 'SpatialPolygons'), margin_samp, 'vertex')] <- 1
proj_margin_samp <- inla.mesh.projector(margin_samp, dims = c(NPIX_X, NPIX_Y))

Bei Dataset with gridding

Count the points in grid cells and fit a Poisson GLMM with the observed area in the cell as an exposure variable, as done in Illian, Sørbye, and Rue (2012) and many other examples. The plots of the cell counts are blank (white) where cells have no observed area.

NGRID_X <- 40
NGRID_Y <- 20

centers <- gridcenters(
  dilation(bei_window_full, max(NGRID_X, NGRID_Y)),
  NGRID_X, NGRID_Y)
dx <- sum(unique(centers$x)[1:2] * c(-1, 1)) / 2
dy <- sum(unique(centers$y)[1:2] * c(-1, 1)) / 2

bei_df <- data.frame(x = centers$x, y = centers$y,
                     count = NA_integer_, area = NA_real_)

system.time(
for(r in seq_len(nrow(bei_df))){
  bei_df$count[r] <- sum(bei$x >= bei_df$x[r] - dx &
                         bei$x < bei_df$x[r] + dx &
                         bei$y >= bei_df$y[r] - dy &
                         bei$y < bei_df$y[r] + dy)
  bei_df$area[r] <- area(Window(bei)[owin(c(bei_df$x[r] - dx, bei_df$x[r] + dx), c(bei_df$y[r] - dy, bei_df$y[r] + dy))])
}
)
   user  system elapsed 
  0.472   0.004   0.474 
par(mar = c(0.5, 0, 2, 2))
plot(im(t(matrix(ifelse(bei_df$area > 0, bei_df$count, NA), nrow = length(unique(bei_df$x)))), unique(bei_df$x), unique(bei_df$y), unitname = 'meters'), ncolcours = range(bei_df$count) %*% c(-1, 1) + 1, main = 'Binned Tree Counts')
plot(bei_window_full, border = 'white', add = TRUE)
points(bei, pch = '.', col = 'black')

# SPDE projector matrix for estimation.
full_A_est <- inla.spde.make.A(
  margin_mesh,
  as.matrix(bei_df[bei_df$area > 0, c('x', 'y')])
)

# Set up stack for estimation.
full_stack_index <- inla.spde.make.index(name = 'spatial_field', n.spde = margin_spde$n.spde)
full_stack_est <- inla.stack(data = list(count = bei_df$count[bei_df$area > 0], larea = log(bei_df$area[bei_df$area > 0])), A = list(full_A_est), effects = list(c(full_stack_index, list(intercept = 1))), tag = 'est')

# SPDE projector matrix for prediction.
full_A_pred <- inla.spde.make.A(mesh = margin_mesh, loc = as.matrix(bei_df[,c('x', 'y')]))

# Set up stacks for prediction.
full_stack_latent <- inla.stack(data = list(xi = NA), A = list(full_A_pred), effects = list(full_stack_index), tag = 'pred_latent')
full_stack_response <- inla.stack(data = list(count = NA), A = list(full_A_pred), effects = list(c(full_stack_index, list(intercept = 1))), tag = 'pred_response')

# Join all three stacks.
full_stacks <- inla.stack(full_stack_est, full_stack_latent, full_stack_response)

# Fit the model with INLA.
system.time(
bei_full_fit <- inla(
  count ~ -1 + intercept + f(spatial_field, model = margin_spde),
  offset = larea, family = 'poisson',
  data = inla.stack.data(full_stacks),
  control.predictor = list(A = inla.stack.A(full_stacks), compute = TRUE),
  verbose = TRUE
)
)
   user  system elapsed 
904.840   0.452 122.519 
# Output posterior summaries.
bei_full_fit$summary.fixed
bei_full_fit$summary.hyperpar
# Plot surface.
par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_mesh, bei_full_fit$summary.fixed$mean + bei_full_fit$summary.random$spatial_field$mean)),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior Mean of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
points(bei, pch = '.', col = 'white')

par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_mesh, sqrt(bei_full_fit$summary.fixed$sd^2 + bei_full_fit$summary.random$spatial_field$sd^2))),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior SD of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
points(bei, pch = '.', col = 'white')

bei_hole_df <- data.frame(x = centers$x, y = centers$y,
                          count = NA_integer_, area = NA_real_)

system.time(
for(r in seq_len(nrow(bei_hole_df))){
  bei_hole_df$count[r] <- sum(bei_hole$x >= bei_hole_df$x[r] - dx &
                              bei_hole$x < bei_hole_df$x[r] + dx &
                              bei_hole$y >= bei_hole_df$y[r] - dy &
                              bei_hole$y < bei_hole_df$y[r] + dy)
  bei_hole_df$area[r] <- area(Window(bei_hole)[owin(c(bei_hole_df$x[r] - dx, bei_hole_df$x[r] + dx), c(bei_hole_df$y[r] - dy, bei_hole_df$y[r] + dy))])
}
)
   user  system elapsed 
  1.168   0.000   1.170 
par(mar = c(0.5, 0, 2, 2))
plot(im(t(matrix(ifelse(bei_hole_df$area > 0, bei_hole_df$count, NA), nrow = length(unique(bei_hole_df$x)))), unique(bei_hole_df$x), unique(bei_hole_df$y), unitname = 'meters'), ncolcours = range(bei_hole_df$count) %*% c(-1, 1) + 1, main = 'Binned Tree Counts')
plot(Window(bei_hole), border = 'white', add = TRUE)
points(bei_hole, pch = '.', col = '#00000040')

# SPDE projector matrix for estimation.
hole_A_est <- inla.spde.make.A(
  margin_hole,
  as.matrix(bei_hole_df[bei_hole_df$area > 0, c('x', 'y')])
)

# Set up stack for estimation.
hole_stack_index <- inla.spde.make.index(name = 'spatial_field', n.spde = margin_hole_spde$n.spde)
hole_stack_est <- inla.stack(data = list(count = bei_hole_df$count[bei_hole_df$area > 0], larea = log(bei_hole_df$area[bei_hole_df$area > 0])), A = list(hole_A_est), effects = list(c(hole_stack_index, list(intercept = 1))), tag = 'est')

# SPDE projector matrix for prediction.
hole_A_pred <- inla.spde.make.A(mesh = margin_hole, loc = as.matrix(bei_hole_df[,c('x', 'y')]))

# Set up stacks for prediction.
hole_stack_latent <- inla.stack(data = list(xi = NA), A = list(hole_A_pred), effects = list(hole_stack_index), tag = 'pred_latent')
hole_stack_response <- inla.stack(data = list(count = NA), A = list(hole_A_pred), effects = list(c(hole_stack_index, list(intercept = 1))), tag = 'pred_response')

# Join all three stacks.
hole_stacks <- inla.stack(hole_stack_est, hole_stack_latent, hole_stack_response)

# Fit the model with INLA.
system.time(
bei_hole_fit <- inla(
  count ~ -1 + intercept + f(spatial_field, model = margin_hole_spde),
  offset = larea, family = 'poisson',
  data = inla.stack.data(hole_stacks),
  control.predictor = list(A = inla.stack.A(hole_stacks), compute = TRUE),
  verbose = TRUE
)
)
   user  system elapsed 
833.072   0.320 113.165 
# Output posterior summaries.
bei_hole_fit$summary.fixed
bei_hole_fit$summary.hyperpar
# Plot surface.
par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_hole, bei_hole_fit$summary.fixed$mean + bei_hole_fit$summary.random$spatial_field$mean)),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior Mean of Log-Intensity')
plot(Window(bei_hole), border = 'white', add = TRUE)
points(bei_hole, pch = '.', col = 'white')

par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_hole, sqrt(bei_hole_fit$summary.fixed$sd^2 + bei_hole_fit$summary.random$spatial_field$sd^2))),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior SD of Log-Intensity')
plot(Window(bei_hole), border = 'white', add = TRUE)
points(bei_hole, pch = '.', col = 'white')

bei_samp_df <- data.frame(x = centers$x, y = centers$y,
                          count = NA_integer_, area = NA_real_)

system.time(
for(r in seq_len(nrow(bei_samp_df))){
  bei_samp_df$count[r] <- sum(bei_samp$x >= bei_samp_df$x[r] - dx &
                              bei_samp$x < bei_samp_df$x[r] + dx &
                              bei_samp$y >= bei_samp_df$y[r] - dy &
                              bei_samp$y < bei_samp_df$y[r] + dy)
  bei_samp_df$area[r] <- area(Window(bei_samp)[owin(c(bei_samp_df$x[r] - dx, bei_samp_df$x[r] + dx), c(bei_samp_df$y[r] - dy, bei_samp_df$y[r] + dy))])
}
)
   user  system elapsed 
  1.060   0.000   1.059 
par(mar = c(0.5, 0, 2, 2))
plot(im(t(matrix(ifelse(bei_samp_df$area > 0, bei_samp_df$count, NA), nrow = length(unique(bei_samp_df$x)))), unique(bei_samp_df$x), unique(bei_samp_df$y), unitname = 'meters'), ncolcours = range(bei_samp_df$count) %*% c(-1, 1) + 1, main = 'Binned Tree Counts')
plot(Window(bei_samp), border = 'white', add = TRUE)
points(bei_samp, pch = '.', col = '#00000040')

# SPDE projector matrix for estimation.
samp_A_est <- inla.spde.make.A(
  margin_samp,
  as.matrix(bei_samp_df[bei_samp_df$area > 0, c('x', 'y')])
)

# Set up stack for estimation.
samp_stack_index <- inla.spde.make.index(name = 'spatial_field', n.spde = margin_samp_spde$n.spde)
samp_stack_est <- inla.stack(data = list(count = bei_samp_df$count[bei_samp_df$area > 0], larea = log(bei_samp_df$area[bei_samp_df$area > 0])), A = list(samp_A_est), effects = list(c(samp_stack_index, list(intercept = 1))), tag = 'est')

# SPDE projector matrix for prediction.
samp_A_pred <- inla.spde.make.A(mesh = margin_samp, loc = as.matrix(bei_samp_df[,c('x', 'y')]))

# Set up stacks for prediction.
samp_stack_latent <- inla.stack(data = list(xi = NA), A = list(samp_A_pred), effects = list(samp_stack_index), tag = 'pred_latent')
samp_stack_response <- inla.stack(data = list(count = NA), A = list(samp_A_pred), effects = list(c(samp_stack_index, list(intercept = 1))), tag = 'pred_response')

# Join all three stacks.
samp_stacks <- inla.stack(samp_stack_est, samp_stack_latent, samp_stack_response)

# Fit the model with INLA.
system.time(
bei_samp_fit <- inla(
  count ~ -1 + intercept + f(spatial_field, model = margin_samp_spde),
  offset = larea, family = 'poisson',
  data = inla.stack.data(samp_stacks),
  control.predictor = list(A = inla.stack.A(samp_stacks), compute = TRUE),
  verbose = TRUE
)
)
   user  system elapsed 
275.936   0.284  38.750 
# Output posterior summaries.
bei_samp_fit$summary.fixed
bei_samp_fit$summary.hyperpar
# Plot surface.
par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_samp, bei_samp_fit$summary.fixed$mean + bei_samp_fit$summary.random$spatial_field$mean)),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior Mean of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
plot(Window(bei_samp), border = 'white', add = TRUE)
points(bei_samp, pch = '.', col = 'white')

par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_samp, sqrt(bei_samp_fit$summary.fixed$sd^2 + bei_samp_fit$summary.random$spatial_field$sd^2))),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior SD of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
plot(Window(bei_samp), border = 'white', add = TRUE)
points(bei_samp, pch = '.', col = 'white')

Bei Dataset with Simpson et al. (2016) method

This method relies upon the Lindgren, Rue, and Lindström (2011) approximation of the latent Gaussian field as a linear combination of a finite number of basis functions represented as a GMRF on the nodes of a triangulation of the space. Simpson et al. (2016) use the triangulation for numerical integration of the intensity function and show that the LGCP likelihood factors into the joint distribution of independent Poisson random variables corresponding to the points of the point pattern and the nodes of the triangulation. The model fitting proceeds using INLA to fit a Poisson model to pseudodata.

The pseudodata are constructed as follows.

Then \(y_{i} \sim Poisson(\alpha_{i}\eta_{i})\) where \(\log(\eta_{i})\) is the SPDE representation of the GF at the location of the \(i\)th pseudodatum. See the paper for tedious notation regarding the definition of \(\eta_{i}\). Ultimately, the nodes become Poisson random variables with means equal to their respective integration weights times the intensity at that their locations (so the integration weight is an exposure variable), observed points become Poisson random variables with means of 1, and the likelihood is approximately proportional to

\[\prod_{i=1}^{p+n} \eta_{i}^{y_{i}} \exp(-\alpha_{i} \eta_{i}).\]

(Is there a missing \(\alpha_{i}\)?)

Covariates can easily be included when they are observed at the mesh nodes. Known “sampling effort” is accomodated by scaling the integration weights by the probability that a point would have been observed at that node (given the sampling plan), i.e. nodes in observed regions have the \(\alpha_{i}\) defined above and nodes in unobserved regions have \(\alpha_{i} = 0\). Weights can be scaled by values other than 0 or 1 to account for e.g. distance sampling with a (known) detection function or a (known) false negative rate for detection equipment. This adjustment assumes the point process of interest is observed through a known thinning process and then allows inference back to the intensity function of the unthinned process. More complicated detection processes are possible, e.g. Yuan et al. (2017) fit an LGCP to data obtianed through distance sampling while using splines to model the detection function.

full_pts <- cbind(bei$x, bei$y)

# Contruct the SPDE A matrix for nodes and points.
full_nV <- margin_mesh$n
full_nData <- dim(full_pts)[1]
full_LocationMatrix <- inla.mesh.project(margin_mesh, full_pts)$A
full_IntegrationMatrix <- sparseMatrix(i = 1:full_nV, j = 1:full_nV, x = rep(1, full_nV))
full_ObservationMatrix <- rbind(full_IntegrationMatrix, full_LocationMatrix)

# Get the integration weights.
full_IntegrationWeights <- diag(inla.mesh.fem(margin_mesh)$c0)
full_E_point_process <- c(obs_full * full_IntegrationWeights, rep(0, full_nData))

# Create the psuedodata.
full_fake_data <- c(rep(0, full_nV), rep(1, full_nData))

# Fit model to full site.
full_formula <- y ~ -1 + intercept + f(idx, model = margin_spde) # No covariates.
full_data <- list(y = full_fake_data, idx = 1:full_nV, intercept = rep(1, full_nV))

system.time(
result_full <- inla(
  formula = full_formula,
  data = full_data,
  family = 'poisson',
  control.predictor = list(A = full_ObservationMatrix),
  E = full_E_point_process,
  verbose = TRUE
)
)
   user  system elapsed 
446.200   0.532  65.662 
result_full$summary.fixed
result_full$summary.hyperpar
# Plot surface.
par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_mesh, result_full$summary.fixed$mean + result_full$summary.random$idx$mean)),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior Mean of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
points(bei, pch = '.', col = 'white')

par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_mesh, sqrt(result_full$summary.fixed$sd^2 + result_full$summary.random$idx$sd^2))),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior SD of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
points(bei, pch = '.', col = 'white')

hole_pts <- cbind(bei_hole$x, bei_hole$y)

# Contruct the SPDE A matrix for nodes and points.
hole_nV <- margin_hole$n
hole_nData <- dim(hole_pts)[1]
hole_LocationMatrix <- inla.mesh.project(margin_hole, hole_pts)$A
hole_IntegrationMatrix <- sparseMatrix(i = 1:hole_nV, j = 1:hole_nV, x = rep(1, hole_nV))
hole_ObservationMatrix <- rbind(hole_IntegrationMatrix, hole_LocationMatrix)

# Get the integration weights.
hole_IntegrationWeights <- diag(inla.mesh.fem(margin_hole)$c0)
hole_E_point_process <- c(obs_hole * hole_IntegrationWeights, rep(0, hole_nData))

# Create the psuedodata.
hole_fake_data <- c(rep(0, hole_nV), rep(1, hole_nData))

# Fit model to site with holes.
hole_formula <- y ~ -1 + intercept + f(idx, model = margin_hole_spde) # No covariates.
hole_data <- list(y = hole_fake_data, idx = 1:hole_nV, intercept = rep(1, hole_nV))

system.time(
result_hole <- inla(
  formula = hole_formula,
  data = hole_data,
  family = 'poisson',
  control.predictor = list(A = hole_ObservationMatrix),
  E = hole_E_point_process,
  verbose = TRUE
)
)
   user  system elapsed 
383.692   0.448  56.575 
result_hole$summary.fixed
result_hole$summary.hyperpar
# Plot surface.
par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_hole, result_hole$summary.fixed$mean + result_hole$summary.random$idx$mean)),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior Mean of Log-Intensity')
plot(Window(bei_hole), border = 'white', add = TRUE)
points(bei_hole, pch = '.', col = 'white')

par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_hole, sqrt(result_hole$summary.fixed$sd^2 + result_hole$summary.random$idx$sd^2))),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior SD of Log-Intensity')
plot(Window(bei_hole), border = 'white', add = TRUE)
points(bei_hole, pch = '.', col = 'white')

samp_pts <- cbind(bei_samp$x, bei_samp$y)

# Contruct the SPDE A matrix for nodes and points.
samp_nV <- margin_samp$n
samp_nData <- dim(samp_pts)[1]
samp_LocationMatrix <- inla.mesh.project(margin_samp, samp_pts)$A
samp_IntegrationMatrix <- sparseMatrix(i = 1:samp_nV, j = 1:samp_nV, x = rep(1, samp_nV))
samp_ObservationMatrix <- rbind(samp_IntegrationMatrix, samp_LocationMatrix)

# Get the integration weights.
samp_IntegrationWeights <- diag(inla.mesh.fem(margin_samp)$c0)
samp_E_point_process <- c(obs_samp * samp_IntegrationWeights, rep(0, samp_nData))

# Create the psuedodata.
samp_fake_data <- c(rep(0, samp_nV), rep(1, samp_nData))

# Fit model to quadrat-sampled site.
samp_formula <- y ~ -1 + intercept + f(idx, model = margin_samp_spde) # No covariates.
samp_data <- list(y = samp_fake_data, idx = 1:samp_nV, intercept = rep(1, samp_nV))

system.time(
result_samp <- inla(
  formula = samp_formula,
  data = samp_data,
  family = 'poisson',
  control.predictor = list(A = samp_ObservationMatrix),
  E = samp_E_point_process,
  verbose = TRUE
)
)
   user  system elapsed 
348.300   0.808  52.568 
result_samp$summary.fixed
result_samp$summary.hyperpar
# Plot surface.
par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_samp, result_samp$summary.fixed$mean + result_samp$summary.random$idx$mean)),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior Mean of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
plot(Window(bei_samp), border = 'white', add = TRUE)
points(bei_samp, pch = '.', col = 'white')

par(mar = c(0.5, 0, 2, 2))
plot(im(t(inla.mesh.project(proj_margin_samp, sqrt(result_samp$summary.fixed$sd^2 + result_samp$summary.random$idx$sd^2))),
        xrange = Frame(bei)$x + c(-MARGIN, MARGIN),
        yrange = Frame(bei)$y + c(-MARGIN, MARGIN),
        unitname = c('meter', 'meters')),
        main = 'Posterior SD of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
plot(Window(bei_samp), border = 'white', add = TRUE)
points(bei_samp, pch = '.', col = 'white')

Bei Dataset and inlabru

inlabru has a wrapper function to fit an LGCP with INLA, with a relatively easy-to-use interface for defining models and predicting arbitrary functions of latent variables. However, it is poorly documented, slow, and the documentation seems to imply that it does not support variable sampling effort (even though this appears to work).

bei_full_spdf <- as.SpatialPoints.ppp(bei)
cmp_full <- coordinates ~ mySmooth(map = coordinates, model = margin_spde) + Intercept

system.time(
bei_full_lgcp <- lgcp(cmp_full, bei_full_spdf, E = obs_full, options = list(verbose = TRUE))
)
    user   system  elapsed 
3776.056    1.712  504.862 
bei_full_lgcp$summary.fixed
bei_full_lgcp$summary.hyperpar
# Plot posterior means and posterior sd.
lambda_full <- predict(bei_full_lgcp, pixels(margin_mesh), ~ Intercept + mySmooth)
plot(lambda_full, main = 'Posterior Mean of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
points(bei, pch = '.', col = 'white')

plot(lambda_full['sd'] ,main = 'Posterior SD of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
points(bei, pch = '.', col = 'white')

bei_hole_spdf <- as.SpatialPoints.ppp(bei_hole)
cmp_hole <- coordinates ~ mySmooth(map = coordinates, model = margin_hole_spde) + Intercept

system.time(
bei_hole_lgcp <- lgcp(cmp_hole, bei_hole_spdf, E = obs_hole, options = list(verbose = TRUE))
)
    user   system  elapsed 
3369.264    1.652  437.205 
bei_hole_lgcp$summary.fixed
bei_hole_lgcp$summary.hyperpar
# Plot posterior means and posterior sd.
lambda_hole <- predict(bei_hole_lgcp, pixels(margin_hole), ~ Intercept + mySmooth)
plot(lambda_hole, main = 'Posterior Mean of Log-Intensity')
plot(Window(bei_hole), border = 'white', add = TRUE)
points(bei_hole, pch = '.', col = 'white')

plot(lambda_hole['sd'], main = 'Posterior SD of Log-Intensity')
plot(Window(bei_hole), border = 'white', add = TRUE)
points(bei_hole, pch = '.', col = 'white')

bei_samp_spdf <- as.SpatialPoints.ppp(bei_samp)
cmp_samp <- coordinates ~ mySmooth(map = coordinates, model = margin_samp_spde) + Intercept

system.time(
bei_samp_lgcp <- lgcp(cmp_samp, bei_samp_spdf, E = obs_samp, options = list(verbose = TRUE))
)
   user  system elapsed 
689.148   1.480 101.756 
bei_samp_lgcp$summary.fixed
bei_samp_lgcp$summary.hyperpar
# Plot posterior means and posterior sd.
lambda_samp <- predict(bei_samp_lgcp, pixels(margin_samp), ~ Intercept + mySmooth)
plot(lambda_samp, main = 'Posterior Mean of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
plot(Window(bei_samp), border = 'white', add = TRUE)
points(bei_samp, pch = '.', col = 'white')

plot(lambda_samp['sd'], main = 'Posterior SD of Log-Intensity')
plot(Window(bei), border = 'white', add = TRUE)
plot(Window(bei_samp), border = 'white', add = TRUE)
points(bei_samp, pch = '.', col = 'white')

Summary

All three methods give similar results for the full dataset and the dataset with holes, even when the gridding method uses a coarse grid. The intercept and random effects are shifted from method to method, but these are not separately indentifiable and the shifts cancel each other out. The methods each have different artifacts and edge effects apparent in the results from the sampled dataset. The pseudodata approach is the fastest except when a very coarse grid is used and a small region is observed.

References

Baddeley, Adrian, and Rolf Turner. 2005. “Spatstat: An R Package for Analyzing Spatial Point Patterns.” Journal of Statistical Software 12 (6): 1–42.

Blangiardo, Marta, and Michela Cameletti. 2015. Spatial and Spatio-Temporal Bayesian Models with R-INLA. Wiley.

Illian, Janine B, Sigrunn H Sørbye, and Håvard Rue. 2012. “A Toolbox for Fitting Complex Spatial Point Process Models Using Integrated Nested Laplace Approximation (Inla).” The Annals of Applied Statistics, 1499–1530.

Lindgren, Finn, Håvard Rue, and Johan Lindström. 2011. “An Explicit Link Between Gaussian Fields and Gaussian Markov Random Fields: The Stochastic Partial Differential Equation Approach.” Journal of the Royal Statistical Society: Series B (Statistical Methodology) 73 (4): 423–98.

Møller, J, and RP Waagepetersen. 2007. “Modern Spatial Point Process Modelling and Inference.” Scandinavian Journal of Statistics 34: 643–711.

Simpson, Daniel, Janine B Illian, Finn Lindgren, Sigrunn H Sørbye, and Havard Rue. 2016. “Going Off Grid: Computationally Efficient Inference for Log-Gaussian Cox Processes.” Biometrika 103 (1): 49–70.

Yuan, Yuan, Fabian E Bachl, Finn Lindgren, David L Borchers, Janine B Illian, Stephen T Buckland, Haavard Rue, Tim Gerrodette, and others. 2017. “Point Process Models for Spatio-Temporal Distance Sampling Data from a Large-Scale Survey of Blue Whales.” The Annals of Applied Statistics 11 (4): 2270–97.

LS0tCnRpdGxlOiAiU3BhdGlhbCBQcmVkaWN0aW9uIHdpdGggSU5MQSIKYXV0aG9yOiAiS2VubmV0aCBBLiBGbGFnZyIKYmlibGlvZ3JhcGh5OiAiLi4vcmVmZXJlbmNlcy5iaWIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgZmlnX2hlaWdodDogNgogICAgZmlnX3dpZHRoOiAxMAogICAgZmlnX2Nyb3A6IEZBTFNFCiAgICBoZWlnaHQ6ICI5NjBweCIKICAgIHdpZHRoOiAiNzIwcHgiCiAgICBzZWxmX2NvbnRhaW5lZDogVFJVRQotLS0KCgpgYGB7ciBzZXR1cCwgY2FjaGUgPSBGQUxTRSwgZWNobyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNhY2hlID0gRkFMU0UsIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsCiAgbWVzc2FnZSA9IEZBTFNFLCBkcGkgPSAxNTAsIGZpZy5hbGlnbiA9ICdjZW50ZXInKQpgYGAKCmBgYHtyIHBhY2thZ2VzLCBlY2hvID0gRkFMU0V9CmxpYnJhcnkoc3BhdHN0YXQpCmxpYnJhcnkoSU5MQSkKbGlicmFyeShpbmxhYnJ1KQpsaWJyYXJ5KG1hcHRvb2xzKQpgYGAKCgojIEludHJvZHVjdGlvbgoKVGhpcyB2aWduZXR0ZSBpbGx1c3RyYXRlcyB0aGUgdXNlIG9mIElOTEEgZm9yIHNwYXRpYWwgcHJlZGljdGlvbiB1c2luZyBleGFtcGxlcwpmcm9tIEByaW5sYSBhbmQgYSBjbGFzc2ljIHBvaW50IHByb2Nlc3MgZGF0YXNldC4gRm9yIHByZWRpY3Rpb24gb2YgY29udGludW91cwpzcGF0aWFsIHByb2Nlc3NlcywgdGhlIEBsaW5kZ3JlbmV0YWwgc3RvY2hhc3RpYyBwYXJ0aWFsIGRpZmZlcmVudGlhbCBlcXVhdGlvbnMKKFNQREUpIGFwcHJvYWNoIGlzIHVzZWQgdG8gYXBwcm94aW1hdGUgdGhlIHByb2Nlc3MgdGhyb3VnaCBhbiBhcmVhbCBHYXVzc2lhbgpNYXJrb3YgcmFuZG9tIGZpZWxkIChHTVJGKSByZXByZXNlbnRhdGlvbi4gRmluYWxseSwgTG9nLUdhdXNzaWFuIENveCBwcm9jZXNzCm1vZGVscyBhcmUgZml0IHVzaW5nIHRoZSBwc2V1ZG9kYXRhIGFwcHJvYWNoIG9mIEBzaW1wc29uZXRhbC4KCgojIEdNUkYgQmFja2dyb3VuZAoKQHJpbmxhIHNlY3Rpb24gNi4xLgoKLSBPYnNlcnZhdGlvbnMgYWdncmVnYXRlZCB0byBkaXNqb2ludCBhcmVhbCByZWdpb25zIGluZGV4ZWQgYnkgJGkkLgotIEVhY2ggcmVnaW9uIGhhcyB1bmlxdWUgcGFyYW1ldGVyICRcdGhldGFfe2l9JC4KLSAkXG1hdGhjYWx7Tn0oaSkkIGlzIHRoZSBzZXQgb2YgaW5kaWNlcyBvZiBuZWlnaGJvcnMgb2YgcmVnaW9uICRpJCBhbmQKICAkXG1hdGhjYWx7Tn1fe2l9ID0gfFxtYXRoY2Fse059KGkpfCQgaXMgdGhlIG51bWJlciBvZiBuZWlnaGJvciBvZiByZWdpb24gJGkkLgotIExvY2FsIE1hcmtvdiBwcm9wZXJ0eTogZ2l2ZW4gJFxib2xkc3ltYm9se1x0aGV0YX1fe1xtYXRoY2Fse059KGkpfSQsCiAgJFx0aGV0YV97aX0kIGlzIGluZGVwZW5kZW50IG9mIGFsbCBvdGhlciAkXHRoZXRhX3tqfSQuCi0gVGhlbiB0aGUgcHJlY2lzaW9uIG1hdHJpeCAkXG1hdGhiZntRfSQgb2YgJFxib2xkc3ltYm9se1x0aGV0YX0kIGlzIHNwYXJzZQogIGJlY2F1c2Ugb25seSBuZWlnaGJvcnMgaGF2ZSBub256ZXJvIGNvcHJlY2lzaW9ucy4KLSBCZXNhZy1Zb3JrLU1vbGxpJiN4MDBlODsgbW9kZWw6CiAgICAtIEV4Y2hhbmdlYWJsZSByYW5kb20gZWZmZWN0cyAkdV97aX0kIHdpdGggaW50cmluc2ljIGNvbmRpdGlvbmFsCiAgICAgIGF1dG9yZWdyZXNzaXZlIChpQ0FSKSBzdHJ1Y3R1cmUuCiAgICAtICR1X3tpfXxcbWF0aGJme3V9X3staX0gXHNpbSBcbWF0aHJte059XGxlZnQoXG11X3tpfSArIFxzdW1fe2p9IGFfe2lqfSAodV97an0gLSBcbXVfe2p9KSAvIFxtYXRoY2Fse059X3tpfSwgXHNpZ21hX3t1fV57Mn0gLyBcbWF0aGNhbHtOfV97aX1ccmlnaHQpJAogICAgLSAoV2hhdCBhcmUgdGhlICRhX3tpan0/JCkuCiAgICAtIGlDQVIgaXMgYW4gaW1wcm9wZXIgcHJpb3IgYmVjYXVzZSBjb3ZhcmlhbmNlIG1hdHJpeCBub3QgcG9zaXRpdmUgZGVmaW5pdGUKICAgICAgYnV0IHRoaXMgaXMgb2sgZm9yIHJhbmRvbSBlZmZlY3RzLgoKCiMgU1BERSBCYWNrZ3JvdW5kCgotIFNwYXRpYWwgcHJvY2VzcyAkWihcbWF0aGJme3N9KSQgd2l0aCAkZCQtZGltZW5zaW9uYWwgZG9tYWluIGlzIHRvIGJlIG1vZGVsZWQuCi0gU29sdmUgJChca2FwcGFeezJ9IC0gXERlbHRhKV57XGFscGhhIC8gMn0gKFx0YXUgWihcbWF0aGJme3N9KSkKICA9IFcoXG1hdGhiZntzfSkkLgogICAgLSAkXGthcHBhJCBpcyBhIHJhbmdlIHBhcmFtZXRlci4KICAgIC0gJFxEZWx0YSQgaXMgdGhlIExhcGxhY2lhbi4KICAgIC0gJFxhbHBoYSQgaXMgYSBzbW9vdGhuZXNzIHBhcmFtZXRlci4KICAgIC0gJFx0YXUkIGlzIGEgcHJlY2lzaW9uIHBhcmFtZXRlci4KICAgIC0gJFcoXG1hdGhiZntzfSkkIGlzIGEgR2F1c3NpYW4gd2hpdGUgbm9pc2UgcHJvY2VzcyB3aXRoIG1lYW4gMCBhbmQKICAgICAgdmFyaWFuY2UgMS4KICAgIC0gRXhhY3Qgc29sdXRpb24gZm9yIHN0YXRpb25hcnkgaXNvdHJvcGljIHByb2Nlc3NlcyBhbmQgaW50ZWdlciAkXGFscGhhJDoKICAgICAgJFooXG1hdGhiZntzfSkkIGlzIGEgR01SRiB0aGF0IHdlbGwtYXBwcm94aW1hdGVzIGEgR2F1c3NpYW4gZmllbGQgd2l0aAogICAgICBNYXQmI3gwMGU4O3JuIGNvdmFyaWFuY2UgZnVuY3Rpb24gd2l0aCAkXG51ID0gXGFscGhhIC0gZC8yJC4KICAgICAgICAtIE5vdGUgZm9yICRkID0gMiQgdGhpcyBleGNsdWRlcyB0aGUgZXhwb25lbnRpYWwgY292YXJpYW5jZSAoJFxudSA9IDEvMiQpLgogICAgLSBTb21lIG90aGVyIHJlc3VsdHMgZm9yIG5vbi1zdGF0aW9uYXJ5IGFuZCBhbmlzb3Ryb3BpYyBwcm9jZXNzZXMuCi0gRmluaXRlIGVsZW1lbnQgYXBwcm94aW1hdGlvbiAkWihcbWF0aGJme3N9KSA9IFxzdW1fe2kgPSAxfV57bn0gel97aX0gXHBzaV97aX0oXG1hdGhiZntzfSkkCiAgd2hlcmUgJFxtYXRoYmZ7en0gPSAoel97MX0sIFxkb3RzLCB6X3tufSknJCBpcyBtdWx0aXZhcmlhdGUgbm9ybWFsIGFuZAogICRce1xwc2lfe2l9KFxtYXRoYmZ7c30pXH1fe2kgPSAxfV57bn0kIGlzIGEgc2V0IG9mIHBpZWNld2lzZSBsaW5lYXIgYmFzaXMKICBmdW5jdGlvbnMuCiAgICAtIFNwZWNpZmljYWxseSwgJFxwc2lfe2l9KFxtYXRoYmZ7c30pID0gMSQgYXQgdGhlICRpJHRoIG5vZGUgb2YgYQogICAgICB0cmlhbmd1bGF0aW9uIG9mIHRoZSBkb21haW4gYW5kIDAgZWxzZXdoZXJlLgogICAgLSAkWihcbWF0aGJme3N9KSQgZm9yIGFyYml0cmFyeSAkXG1hdGhiZntzfSQgZm91bmQgYnkgbGluZWFyIGludGVycG9sYXRpb24uCi0gSW1wbGVtZW50ZWQgYXMgYSBibGFjayBib3ggaW4gSU5MQS4KICAgIC0gVXNlciBkZWZpbmVzIHRoZSB0cmlhbmd1bGF0aW9uIGJ1dCBuZXZlciBzZWVzIHRoZSBHTVJGIHJlcHJlc2VudGF0aW9uLgotIFJlZHVjZXMgY292YXJpYW5jZS9wcmVjaXNpb24gbWF0cml4IGNhbGN1bGF0aW9ucyBmcm9tICRcbWF0aGNhbHtPfShuXnszfSkkCiAgdG8gJFxtYXRoY2Fse099KG5eezMvMn0pJC4KCgojIEdlb3N0YXRpc3RpY3MgRXhhbXBsZQoKVG95IGRhdGFzZXQgZnJvbSBAcmlubGEuIEEgc3BhdGlhbCBwcm9jZXNzIGlzIG9ic2VydmVkIHZpYSBhIHJhbmRvbSBzYW1wbGluZwpwbGFuIHRoYXQgY29uY2VudHJhdGVzIG9ic2VydmF0aW9ucyBpbiB0aGUgbG93ZXIgbGVmdCBvZiB0aGUgdW5pdCBzcXVhcmUuCgpgYGB7ciBzcGRldG95LCBmaWcud2lkdGggPSA2LCBvdXQud2lkdGggPSAnNjAlJ30KIyBQbG90IHRoZSBkYXRhLgpwbG90KHMyIH4gczEsIGNvbCA9IHJnYihTUERFdG95JHkgLyBtYXgoU1BERXRveSR5KSwgMCwgMCksIGRhdGEgPSBTUERFdG95LCBwY2ggPSAxOSwgYXNwID0gMSwgbWFpbiA9ICdUb3kgRGF0YScpCmBgYAoKYGBge3Igc3BkZW1lc2gsIGZpZy53aWR0aCA9IDYsIG91dC53aWR0aCA9ICc2MCUnfQojIENyZWF0ZSBhIG1lc2ggZm9yIHRoZSBTUERFIG1ldGhvZCBhbmQgdGhlbiBwbG90IGl0Lgp0b3lfbWVzaCA8LSBpbmxhLm1lc2guMmQoYXMubWF0cml4KFNQREV0b3lbLGMoJ3MxJywgJ3MyJyldKSwgbWF4LmVkZ2UgPSBjKDAuMSwgMC4yKSkKcGxvdCh0b3lfbWVzaCwgYXNwID0gMSkKcG9pbnRzKFNQREV0b3kkczEsIFNQREV0b3kkczIsIGNvbCA9IHJnYihTUERFdG95JHkgLyBtYXgoU1BERXRveSR5KSwgMCwgMCwgMC41KSwgcGNoID0gMjApCmBgYAoKYGBge3Igc3BkZWZpdCwgY2FjaGUgPSBUUlVFfQojIFNQREUgcHJvamVjdG9yIG1hdHJpeCBmb3IgZXN0aW1hdGlvbi4KQV9lc3QgPC0gaW5sYS5zcGRlLm1ha2UuQSh0b3lfbWVzaCwgYXMubWF0cml4KFNQREV0b3lbLGMoJ3MxJywgJ3MyJyldKSkKCiMgSW5pdGlhbGl6ZSBleHBvbmVudGlhbCBjb3ZhcmlhbmNlIHN0cnVjdHVyZSBmb3IgU1BERS4Kc3BkZSA8LSBpbmxhLnNwZGUyLm1hdGVybihtZXNoID0gdG95X21lc2gsIGFscGhhID0gMikKCiMgU2V0IHVwIHN0YWNrIGZvciBlc3RpbWF0aW9uLgpzdGFja19pbmRleCA8LSBpbmxhLnNwZGUubWFrZS5pbmRleChuYW1lID0gJ3NwYXRpYWxfZmllbGQnLCBuLnNwZGUgPSBzcGRlJG4uc3BkZSkKc3RhY2tfZXN0IDwtIGlubGEuc3RhY2soZGF0YSA9IGxpc3QoeSA9IFNQREV0b3kkeSksIEEgPSBsaXN0KEFfZXN0KSwgZWZmZWN0cyA9IGxpc3QoYyhzdGFja19pbmRleCwgbGlzdChpbnRlcmNlcHQgPSAxKSkpLCB0YWcgPSAnZXN0JykKCiMgQ3JlYXRlIGEgZ3JpZCBmb3IgcHJlZGljdGlvbi4KdG95X254IDwtIDUwCnRveV9ueSA8LSA1MAp0b3lfZ3JpZCA8LSBleHBhbmQuZ3JpZCh4ID0gc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSB0b3lfbngpLCB5ID0gc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSB0b3lfbnkpKQoKIyBTUERFIHByb2plY3RvciBtYXRyaXggZm9yIHByZWRpY3Rpb24uCkFfcHJlZCA8LSBpbmxhLnNwZGUubWFrZS5BKG1lc2ggPSB0b3lfbWVzaCwgbG9jID0gYXMubWF0cml4KHRveV9ncmlkKSkKCiMgU2V0IHVwIHN0YWNrcyBmb3IgcHJlZGljdGlvbi4Kc3RhY2tfbGF0ZW50IDwtIGlubGEuc3RhY2soZGF0YSA9IGxpc3QoeGkgPSBOQSksIEEgPSBsaXN0KEFfcHJlZCksIGVmZmVjdHMgPSBsaXN0KHN0YWNrX2luZGV4KSwgdGFnID0gJ3ByZWRfbGF0ZW50JykKc3RhY2tfcmVzcG9uc2UgPC0gaW5sYS5zdGFjayhkYXRhID0gbGlzdCh5ID0gTkEpLCBBID0gbGlzdChBX3ByZWQpLCBlZmZlY3RzID0gbGlzdChjKHN0YWNrX2luZGV4LCBsaXN0KGludGVyY2VwdCA9IDEpKSksIHRhZyA9ICdwcmVkX3Jlc3BvbnNlJykKCiMgSm9pbiBhbGwgdGhyZWUgc3RhY2tzLgpzdGFja3MgPC0gaW5sYS5zdGFjayhzdGFja19lc3QsIHN0YWNrX2xhdGVudCwgc3RhY2tfcmVzcG9uc2UpCgojIEZpdCB0aGUgbW9kZWwgd2l0aCBJTkxBLgp0b3lfZml0IDwtIGlubGEoCiAgeSB+IC0xICsgaW50ZXJjZXB0ICsgZihzcGF0aWFsX2ZpZWxkLCBtb2RlbCA9IHNwZGUpLAogIGRhdGEgPSBpbmxhLnN0YWNrLmRhdGEoc3RhY2tzKSwKICBjb250cm9sLnByZWRpY3RvciA9IGxpc3QoQSA9IGlubGEuc3RhY2suQShzdGFja3MpLCBjb21wdXRlID0gVFJVRSkKKQoKIyBPdXRwdXQgcG9zdGVyaW9yIHN1bW1hcmllcy4KdG95X2ZpdCRzdW1tYXJ5LmZpeGVkCnRveV9maXQkc3VtbWFyeS5oeXBlcnBhcgoKIyBFeHRyYWN0IHBvc3RlcmlvciBtZWFuIG9mIGxhdGVudCBzcGF0aWFsIGZpZWxkLgppbmRleF9sYXRlbnQgPC0gaW5sYS5zdGFjay5pbmRleChzdGFja3MsIHRhZyA9ICdwcmVkX2xhdGVudCcpJGRhdGEKcG9zdF9tZWFuIDwtIHRveV9maXQkc3VtbWFyeS5saW5lYXIucHJlZGljdG9yW2luZGV4X2xhdGVudCwgJ21lYW4nXQpwb3N0X3NkIDwtIHRveV9maXQkc3VtbWFyeS5saW5lYXIucHJlZGljdG9yW2luZGV4X2xhdGVudCwgJ3NkJ10KCiMgUGxvdCB0aGUgcG9zdGVyaW9yIG1lYW4gYW5kIFNEIG9mIHRoZSBsYXRlbnQgc3BhdGlhbCBmaWVsZC4KcGxvdChpbShtYXRyaXgocG9zdF9tZWFuLCBucm93ID0gdG95X254LCBuY29sID0gdG95X255KSwgeHJhbmdlID0gcmFuZ2UodG95X2dyaWQkeCksIHlyYW5nZSA9IHJhbmdlKHRveV9ncmlkJHkpKSwgbWFpbiA9ICdQb3N0ZXJpb3IgTWVhbiBvZiBTcGF0aWFsIEZpZWxkJykKcG9pbnRzKFNQREV0b3kkczEsIFNQREV0b3kkczIsIGNvbCA9IHJnYihTUERFdG95JHkgLyBtYXgoU1BERXRveSR5KSwgMCwgMCwgMC41KSwgcGNoID0gMjApCnBsb3QoaW0obWF0cml4KHBvc3Rfc2QsIG5yb3cgPSB0b3lfbngsIG5jb2wgPSB0b3lfbnkpLCB4cmFuZ2UgPSByYW5nZSh0b3lfZ3JpZCR4KSwgeXJhbmdlID0gcmFuZ2UodG95X2dyaWQkeSkpLCBtYWluID0gJ1Bvc3RlcmlvciBTRCBvZiBTcGF0aWFsIEZpZWxkJykKcG9pbnRzKFNQREV0b3kkczEsIFNQREV0b3kkczIsIGNvbCA9IHJnYihTUERFdG95JHkgLyBtYXgoU1BERXRveSR5KSwgMCwgMCwgMC41KSwgcGNoID0gMjApCmBgYAoKIyBCZWkgRGF0YXNldAoKRXhhbXBsZSBmcm9tIEBtb2VsbGVyd2FhZ2VwZXRlcnNlbiwgX0JlaWxzY2htaWVkaWEgcGVuZHVsYSBMYXVyYWNlYWVfIGxvY2F0aW9ucwppbiBhIHBsb3QgaW4gUGFuYW1hLiBgYmVpYCBkYXRhc2V0IGluIGBzcGF0c3RhdGAgW0BzcGF0c3RhdF0uIFRoZSBpbGx1c3RyYXRpb25zCmJlbG93IGZpdCBhIHN0YXRpb25hcnkgTEdDUCBtb2RlbCB3aXRoIG5vIGNvdmFyaWF0ZXMsIGEgTWF0JiN4MDBlODtybgpjb3ZhcmlhbmNlIGZ1bmN0aW9uIHdpdGggJFxudSA9IDEkLCBhbmQgSU5MQSdzIGRlZmF1bHQgXyhmbGF0PylfIHByaW9ycyBmb3IgYWxsCnBhcmFtZXRlcnMuCgpgYGB7ciBiZWlwdHN9CiMgUGxvdCB0aGUgZnVsbCBwb2ludCBwYXR0ZXJuLgpwbG90KGJlaSwgcGNoID0gJy4nLCBjb2xzID0gJ2JsYWNrJywgbWFpbiA9ICdSZWFsaXplZCBQb2ludCBQYXR0ZXJuJykKYGBgCgpgYGB7ciBiZWlob2xlLCBjYWNoZSA9IFRSVUV9CiMgVGFrZSBhIHNhbXBsZSBvZiBxdWFkcmF0cyBhbmQgcGxvdCB0aGUgb2JzZXJ2ZWQgcG9pbnQgcGF0dGVybi4Kc2V0LnNlZWQoODQzMjMpCk5fUVVBRFMgPC0gMTAKUVVBRF9TSVpFIDwtIDUwCgp3X2VkZ2UgPC0gRnJhbWUoYmVpKSR4cmFuZ2VbMV0KZV9lZGdlIDwtIEZyYW1lKGJlaSkkeHJhbmdlWzJdCnNfZWRnZSA8LSBGcmFtZShiZWkpJHlyYW5nZVsxXQpuX2VkZ2UgPC0gRnJhbWUoYmVpKSR5cmFuZ2VbMl0KCmJvdGxlZnQgPC0gY2JpbmQoCiAgcnVuaWYoTl9RVUFEUywgd19lZGdlLCBlX2VkZ2UgLSBRVUFEX1NJWkUpLAogIHJ1bmlmKE5fUVVBRFMsIHNfZWRnZSwgbl9lZGdlIC0gUVVBRF9TSVpFKQopCmJlaV9pbnRlcmlvciA8LSBsYXBwbHkoc2VxX2xlbihucm93KGJvdGxlZnQpKSwgZnVuY3Rpb24ocil7cmV0dXJuKAogICAgY2JpbmQoCiAgICAgIGJvdGxlZnRbciwgMV0gKyBjKDAsIDAsIFFVQURfU0laRSwgUVVBRF9TSVpFKSwKICAgICAgYm90bGVmdFtyLCAyXSArIGMoMCwgUVVBRF9TSVpFLCBRVUFEX1NJWkUsIDApCiAgICApCiAgKX0pCmJlaV93aW4gPC0gZG8uY2FsbCgKICB1bmlvbi5vd2luLAogIGFwcGx5KGJvdGxlZnQsIDEsIGZ1bmN0aW9uKHgpe3JldHVybigKICAgIG93aW4oeFsxXSArIGMoMCwgUVVBRF9TSVpFKSwgeFsyXSArIGMoMCwgUVVBRF9TSVpFKSkKICApfSkKKQpiZWlfaG9sZSA8LSBiZWlbY29tcGxlbWVudC5vd2luKGJlaV93aW4sIGZyYW1lID0gRnJhbWUoYmVpKSldCmJlaV9zYW1wIDwtIGJlaVtiZWlfd2luXQpiZWlfd2luZG93X2Z1bGwgPC0gV2luZG93KGJlaSkKCnBsb3QoYmVpX2hvbGUsIG1haW4gPSAnT2JzZXJ2ZWQgUmVnaW9uIHdpdGggSG9sZXMnLCBwY2ggPSAnLicsIGNvbHMgPSAnYmxhY2snKQpgYGAKCmBgYHtyIGJlaXNhbXAsIGNhY2hlID0gVFJVRSwgZGVwZW5kc29uID0gJ2JlaWhvbGUnfQpwbG90KGJlaV93aW5kb3dfZnVsbCwgbWFpbiA9ICdPYnNlcnZlZCBTYW1wbGUnKQpwbG90KGJlaV93aW4sIGFkZCA9IFRSVUUpCnBsb3QoYmVpX3NhbXAsIHBjaCA9ICcuJywgY29scyA9ICdibGFjaycsIGFkZCA9IFRSVUUpCmBgYAoKIyMgVHJpYW5ndWxhdGlvbiBNZXNoZXMgZm9yIFNQREUgQXBwcm9hY2gKClRoZSBtZXNoIHNob3VsZCBiZSBmaW5lIGluIG9ic2VydmVkIHJlZ2lvbnMgdG8gYWNjdXJhdGVseSByZXByZXNlbnQgY29tcGxleApsb2NhbCBzdHJ1Y3R1cmUgYnV0IGNhbiBiZSBjb2Fyc2VuZWQgaW4gdW5vYnNlcnZlZCByZWdpb25zIHdoZXJlIHRoZSBtb2RlbApjYW5ub3QgaW5mZXIgYXMgbXVjaCBkZXRhaWwuIEkgYW0gaW5jbHVkaW5nIG1hcmdpbnMgdG8gZXhwbG9yZSBtb2RlbCBiZWhhdmlvcgphd2F5IGZyb20gdGhlIG9ic2VydmVkIHJlZ2lvbnMuCgpgYGB7ciBiZWltZXNoLCBjYWNoZSA9IFRSVUUsIGRlcGVuZHNvbiA9ICdiZWlob2xlJ30KIyBQYXJhbWV0ZXJzIHRvIGV4cGVyaW1lbnQgd2l0aC4KTUFYX0VER0VfTEVOR1RIIDwtIDI1Ck1BWF9FREdFX0VYVCA8LSA1MApNQVJHSU4gPC0gMTAwCgojIE1lc2ggY292ZXJpbmcgdGhlIHNpdGUuCmJlaV9ib3VuZGFyeSA8LSBpbmxhLm1lc2guc2VnbWVudChsb2MgPSBkby5jYWxsKGNiaW5kLCB2ZXJ0aWNlcy5vd2luKFdpbmRvdyhiZWkpKSkpCmJlaV9mdWxsX21lc2ggPC0gaW5sYS5tZXNoLmNyZWF0ZSgKICBib3VuZGFyeSA9IGJlaV9ib3VuZGFyeSwKICByZWZpbmUgPSBsaXN0KG1heC5lZGdlID0gTUFYX0VER0VfTEVOR1RIKQopCmJlaV9mdWxsX3NwZGUgPC0gaW5sYS5zcGRlMi5tYXRlcm4obWVzaCA9IGJlaV9mdWxsX21lc2gpCnBsb3QoYmVpX2Z1bGxfbWVzaCwgYXNwID0gMSwgbWFpbiA9ICdGaW5lIE1lc2gnKQpwb2ludHMoYmVpLCBwY2ggPSAxOSwgY2V4ID0gMC4yNSwgY29sID0gJ3JlZCcpCnRpdGxlKCdGaW5lIE1lc2gnKQoKIyBNZXNoIGluY2x1ZGluZyBhIG1hcmdpbiBvdXRzaWRlIHRoZSBzaXRlLgptYXJnaW5fbWVzaCA8LSBpbmxhLm1lc2guMmQoCiAgbG9jID0gYmVpX2Z1bGxfbWVzaCRsb2NbLDE6Ml0sICMgSW5jbHVkZSBub2RlcyBmcm9tIHNpdGUuCiAgb2Zmc2V0ID0gTUFSR0lOLAogIG1heC5lZGdlID0gTUFYX0VER0VfRVhUICMgRmlsbCBpbiB0aGUgcmVzdCB3aXRoIGEgY29hcnNlciB0cmlhbmd1bGF0aW9uLgopCm1hcmdpbl9zcGRlIDwtIGlubGEuc3BkZTIubWF0ZXJuKG1lc2ggPSBtYXJnaW5fbWVzaCkKcGxvdChtYXJnaW5fbWVzaCwgYXNwID0gMSwgbWFpbiA9ICdGaW5lIE1lc2ggd2l0aCBDb2Fyc2UgTWFyZ2luJykKcG9pbnRzKGJlaSwgcGNoID0gMTksIGNleCA9IDAuMjUsIGNvbCA9ICdyZWQnKQp0aXRsZSgnRmluZSBNZXNoIHdpdGggQ29hcnNlIE1hcmdpbicpCgoKIyBNZXNocyB3aXRoIGNvYXJzZXIgcmVzb2x1dGlvbiBpbiBxdWFkcmF0cy4KcXVhZF9ob2xlIDwtIGRvLmNhbGwoCiAgaW5sYS5tZXNoLnNlZ21lbnQsCiAgbGFwcGx5KHNlcV9hbG9uZyhiZWlfaW50ZXJpb3IpLCBmdW5jdGlvbihpKXsKICAgIHJldHVybihpbmxhLm1lc2guc2VnbWVudChsb2MgPSBiZWlfaW50ZXJpb3JbW2ldXSwgZ3JwID0gaSAtIDEpKQogIH0pCikKYmVpX2hvbGVfbWVzaDAgPC0gaW5sYS5tZXNoLmNyZWF0ZSgKICBib3VuZGFyeSA9IGxpc3QoYmVpX2JvdW5kYXJ5LCBxdWFkX2hvbGUpLAogIHJlZmluZSA9IGxpc3QobWF4LmVkZ2UgPSBNQVhfRURHRV9MRU5HVEgpCikKYmVpX2hvbGUwX3NwZGUgPC0gaW5sYS5zcGRlMi5tYXRlcm4obWVzaCA9IGJlaV9ob2xlX21lc2gwKQpwbG90KGJlaV9ob2xlX21lc2gwLCBhc3AgPSAxLCBtYWluID0gJ0ZpbmUgTWVzaCB3aXRoIEhvbGVzJykKcG9pbnRzKGJlaV9ob2xlLCBwY2ggPSAxOSwgY2V4ID0gMC4yNSwgY29sID0gJ3JlZCcpCnRpdGxlKCdGaW5lIE1lc2ggd2l0aCBIb2xlcycpCmJlaV9ob2xlX21lc2ggPC0gaW5sYS5tZXNoLmNyZWF0ZSgKICBsb2MgPSBiZWlfaG9sZV9tZXNoMCRsb2NbLDE6Ml0sICMgSW5jbHVkZSBub2RlcyBmcm9tIG1lc2ggd2l0aCBob2xlcy4KICBib3VuZGFyeSA9IGJlaV9ib3VuZGFyeSwKICByZWZpbmUgPSBsaXN0KG1heC5lZGdlID0gTUFYX0VER0VfRVhUKSAjIEZpbGwgaW4gdGhlIHJlc3Qgd2l0aCBhIGNvYXJzZXIgdHJpYW5ndWxhdGlvbi4KKQpiZWlfaG9sZV9zcGRlIDwtIGlubGEuc3BkZTIubWF0ZXJuKG1lc2ggPSBiZWlfaG9sZV9tZXNoKQpwbG90KGJlaV9ob2xlX21lc2gsIGFzcCA9IDEsIG1haW4gPSAnRmluZSBNZXNoIHdpdGggQ29hcnNlIEhvbGVzJykKcG9pbnRzKGJlaV9ob2xlLCBwY2ggPSAxOSwgY2V4ID0gMC4yNSwgY29sID0gJ3JlZCcpCnRpdGxlKCdGaW5lIE1lc2ggd2l0aCBDb2Fyc2UgSG9sZXMnKQoKIyBNZXNocyB3aXRoIGZpbmVyIHJlc29sdXRpb24gaW4gcXVhZHJhdHMuCnF1YWRfYm5kIDwtIGRvLmNhbGwoCiAgaW5sYS5tZXNoLnNlZ21lbnQsCiAgbGFwcGx5KHNlcV9hbG9uZyhiZWlfaW50ZXJpb3IpLCBmdW5jdGlvbihpKXsKICAgIHJldHVybihpbmxhLm1lc2guc2VnbWVudChsb2MgPSBhcHBseShiZWlfaW50ZXJpb3JbW2ldXSwgMiwgcmV2KSwgZ3JwID0gaSAtIDEpKQogIH0pCikKYmVpX3NhbXBfbWVzaDAgPC0gaW5sYS5tZXNoLmNyZWF0ZSgKICBib3VuZGFyeSA9IHF1YWRfYm5kLAogIHJlZmluZSA9IGxpc3QobWF4LmVkZ2UgPSBNQVhfRURHRV9MRU5HVEgpCikKYmVpX3NhbXAwX3NwZGUgPC0gaW5sYS5zcGRlMi5tYXRlcm4obWVzaCA9IGJlaV9zYW1wX21lc2gwKQpwbG90KGJlaV9zYW1wX21lc2gwLCBhc3AgPSAxLCBtYWluID0gJ0ZpbmUgTWVzaCBpbiBRdWFkcmF0cycpCnBvaW50cyhiZWlfc2FtcCwgcGNoID0gMTksIGNleCA9IDAuMjUsIGNvbCA9ICdyZWQnKQp0aXRsZSgnRmluZSBNZXNoIGluIFF1YWRyYXRzJykKYmVpX3NhbXBfbWVzaCA8LSBpbmxhLm1lc2guY3JlYXRlKAogIGxvYyA9IGJlaV9zYW1wX21lc2gwJGxvY1ssMToyXSwgIyBJbmNsdWRlIG5vZGVzIGZyb20gbWVzaCBpbiBxdWFkcy4KICBib3VuZGFyeSA9IGJlaV9ib3VuZGFyeSwKICByZWZpbmUgPSBsaXN0KG1heC5lZGdlID0gTUFYX0VER0VfRVhUKSAjIEZpbGwgaW4gdGhlIHJlc3Qgd2l0aCBhIGNvYXJzZXIgdHJpYW5ndWxhdGlvbi4KKQpiZWlfc2FtcF9zcGRlIDwtIGlubGEuc3BkZTIubWF0ZXJuKG1lc2ggPSBiZWlfc2FtcF9tZXNoKQpwbG90KGJlaV9zYW1wX21lc2gsIGFzcCA9IDEsIG1haW4gPSAnQ29hcnNlIE1lc2ggd2l0aCBGaW5lIFF1YWRyYXRzJykKcG9pbnRzKGJlaV9zYW1wLCBwY2ggPSAxOSwgY2V4ID0gMC4yNSwgY29sID0gJ3JlZCcpCnRpdGxlKCdDb2Fyc2UgTWVzaCB3aXRoIEZpbmUgUXVhZHJhdHMnKQoKIyBNZXNoZXMgd2l0aCB2YXJ5aW5nIHJlc29sdXRpb24gaW4gcXVhZHJhdHMgYW5kIGEgbWFyZ2luLgptYXJnaW5faG9sZSA8LSBpbmxhLm1lc2guMmQoCiAgbG9jID0gYmVpX2hvbGVfbWVzaCRsb2NbLDE6Ml0sICMgSW5jbHVkZSBub2RlcyBmcm9tIG1lc2ggd2l0aCBob2xlcy4KICBvZmZzZXQgPSBNQVJHSU4sCiAgbWF4LmVkZ2UgPSBNQVhfRURHRV9FWFQgIyBGaWxsIGluIHRoZSByZXN0IHdpdGggYSBjb2Fyc2VyIHRyaWFuZ3VsYXRpb24uCikKbWFyZ2luX2hvbGVfc3BkZSA8LSBpbmxhLnNwZGUyLm1hdGVybihtZXNoID0gbWFyZ2luX2hvbGUpCnBsb3QobWFyZ2luX2hvbGUsIGFzcCA9IDEsIG1haW4gPSAnQ29hcnNlIEhvbGVzIGFuZCBNYXJnaW4nKQpwb2ludHMoYmVpX2hvbGUsIHBjaCA9IDE5LCBjZXggPSAwLjI1LCBjb2wgPSAncmVkJykKdGl0bGUoJ0NvYXJzZSBIb2xlcyBhbmQgTWFyZ2luJykKbWFyZ2luX3NhbXAgPC0gaW5sYS5tZXNoLjJkKAogIGxvYyA9IGJlaV9zYW1wX21lc2gkbG9jWywxOjJdLCAjIEluY2x1ZGUgbm9kZXMgZnJvbSBxdWFkcy4KICBvZmZzZXQgPSBNQVJHSU4sCiAgbWF4LmVkZ2UgPSBNQVhfRURHRV9FWFQgIyBGaWxsIGluIHRoZSByZXN0IHdpdGggYSBjb2Fyc2VyIHRyaWFuZ3VsYXRpb24uCikKbWFyZ2luX3NhbXBfc3BkZSA8LSBpbmxhLnNwZGUyLm1hdGVybihtZXNoID0gbWFyZ2luX3NhbXApCnBsb3QobWFyZ2luX3NhbXAsIGFzcCA9IDEsIG1haW4gPSAnRmluZSBRdWFkcmF0cyBhbmQgQ29hcnNlIE1hcmdpbicpCnBvaW50cyhiZWlfc2FtcCwgcGNoID0gMTksIGNleCA9IDAuMjUsIGNvbCA9ICdyZWQnKQp0aXRsZSgnRmluZSBRdWFkcmF0cyBhbmQgQ29hcnNlIE1hcmdpbicpCmBgYAoKYGBge3IgZWZmb3J0LCBjYWNoZSA9IFRSVUUsIGRlcGVuZHNvbiA9ICdiZWltZXNoJ30KIyBJZGVudGlmeSB3aGljaCBtZXNoIG5vZGVzIGFyZSBpbiB0aGUgb2JlcnNlcnZlZCByZWdpb24KIyBhbmQgY3JlYXRlIFNQREUgcHJvamVjdG9ycyBmb3Igc3BhdGlhbCBtYXBwaW5nLgpOUElYX1ggPC0gNDAwCk5QSVhfWSA8LSAyMDAKCm9ic19mdWxsIDwtIHJlcCgwLCBtYXJnaW5fbWVzaCRuKQpvYnNfZnVsbFtpbmxhLm92ZXJfc3BfbWVzaChhcyhXaW5kb3coYmVpKSwgJ1NwYXRpYWxQb2x5Z29ucycpLCBtYXJnaW5fbWVzaCwgJ3ZlcnRleCcpXSA8LSAxCnByb2pfbWFyZ2luX21lc2ggPC0gaW5sYS5tZXNoLnByb2plY3RvcihtYXJnaW5fbWVzaCwgZGltcyA9IGMoTlBJWF9YLCBOUElYX1kpKQoKb2JzX2hvbGUgPC0gcmVwKDAsIG1hcmdpbl9ob2xlJG4pCm9ic19ob2xlW2lubGEub3Zlcl9zcF9tZXNoKGFzKFdpbmRvdyhiZWlfaG9sZSksICdTcGF0aWFsUG9seWdvbnMnKSwgbWFyZ2luX2hvbGUsICd2ZXJ0ZXgnKV0gPC0gMQpwcm9qX21hcmdpbl9ob2xlIDwtIGlubGEubWVzaC5wcm9qZWN0b3IobWFyZ2luX2hvbGUsIGRpbXMgPSBjKE5QSVhfWCwgTlBJWF9ZKSkKCm9ic19zYW1wIDwtIHJlcCgwLCBtYXJnaW5fc2FtcCRuKQpvYnNfc2FtcFtpbmxhLm92ZXJfc3BfbWVzaChhcyhXaW5kb3coYmVpX3NhbXApLCAnU3BhdGlhbFBvbHlnb25zJyksIG1hcmdpbl9zYW1wLCAndmVydGV4JyldIDwtIDEKcHJval9tYXJnaW5fc2FtcCA8LSBpbmxhLm1lc2gucHJvamVjdG9yKG1hcmdpbl9zYW1wLCBkaW1zID0gYyhOUElYX1gsIE5QSVhfWSkpCmBgYAoKCiMjIEJlaSBEYXRhc2V0IHdpdGggZ3JpZGRpbmcKCkNvdW50IHRoZSBwb2ludHMgaW4gZ3JpZCBjZWxscyBhbmQgZml0IGEgUG9pc3NvbiBHTE1NIHdpdGggdGhlIG9ic2VydmVkIGFyZWEKaW4gdGhlIGNlbGwgYXMgYW4gZXhwb3N1cmUgdmFyaWFibGUsIGFzIGRvbmUgaW4gQGlsbGlhbmV0YWwgYW5kIG1hbnkgb3RoZXIKZXhhbXBsZXMuIFRoZSBwbG90cyBvZiB0aGUgY2VsbCBjb3VudHMgYXJlIGJsYW5rICh3aGl0ZSkgd2hlcmUgY2VsbHMgaGF2ZSBubwpvYnNlcnZlZCBhcmVhLgoKYGBge3IgYmVpaW5sYSwgY2FjaGUgPSBUUlVFLCBkZXBlbmRzb24gPSAnZWZmb3J0J30KTkdSSURfWCA8LSA0MApOR1JJRF9ZIDwtIDIwCgpjZW50ZXJzIDwtIGdyaWRjZW50ZXJzKAogIGRpbGF0aW9uKGJlaV93aW5kb3dfZnVsbCwgbWF4KE5HUklEX1gsIE5HUklEX1kpKSwKICBOR1JJRF9YLCBOR1JJRF9ZKQpkeCA8LSBzdW0odW5pcXVlKGNlbnRlcnMkeClbMToyXSAqIGMoLTEsIDEpKSAvIDIKZHkgPC0gc3VtKHVuaXF1ZShjZW50ZXJzJHkpWzE6Ml0gKiBjKC0xLCAxKSkgLyAyCgpiZWlfZGYgPC0gZGF0YS5mcmFtZSh4ID0gY2VudGVycyR4LCB5ID0gY2VudGVycyR5LAogICAgICAgICAgICAgICAgICAgICBjb3VudCA9IE5BX2ludGVnZXJfLCBhcmVhID0gTkFfcmVhbF8pCgpzeXN0ZW0udGltZSgKZm9yKHIgaW4gc2VxX2xlbihucm93KGJlaV9kZikpKXsKICBiZWlfZGYkY291bnRbcl0gPC0gc3VtKGJlaSR4ID49IGJlaV9kZiR4W3JdIC0gZHggJgogICAgICAgICAgICAgICAgICAgICAgICAgYmVpJHggPCBiZWlfZGYkeFtyXSArIGR4ICYKICAgICAgICAgICAgICAgICAgICAgICAgIGJlaSR5ID49IGJlaV9kZiR5W3JdIC0gZHkgJgogICAgICAgICAgICAgICAgICAgICAgICAgYmVpJHkgPCBiZWlfZGYkeVtyXSArIGR5KQogIGJlaV9kZiRhcmVhW3JdIDwtIGFyZWEoV2luZG93KGJlaSlbb3dpbihjKGJlaV9kZiR4W3JdIC0gZHgsIGJlaV9kZiR4W3JdICsgZHgpLCBjKGJlaV9kZiR5W3JdIC0gZHksIGJlaV9kZiR5W3JdICsgZHkpKV0pCn0KKQoKcGFyKG1hciA9IGMoMC41LCAwLCAyLCAyKSkKcGxvdChpbSh0KG1hdHJpeChpZmVsc2UoYmVpX2RmJGFyZWEgPiAwLCBiZWlfZGYkY291bnQsIE5BKSwgbnJvdyA9IGxlbmd0aCh1bmlxdWUoYmVpX2RmJHgpKSkpLCB1bmlxdWUoYmVpX2RmJHgpLCB1bmlxdWUoYmVpX2RmJHkpLCB1bml0bmFtZSA9ICdtZXRlcnMnKSwgbmNvbGNvdXJzID0gcmFuZ2UoYmVpX2RmJGNvdW50KSAlKiUgYygtMSwgMSkgKyAxLCBtYWluID0gJ0Jpbm5lZCBUcmVlIENvdW50cycpCnBsb3QoYmVpX3dpbmRvd19mdWxsLCBib3JkZXIgPSAnd2hpdGUnLCBhZGQgPSBUUlVFKQpwb2ludHMoYmVpLCBwY2ggPSAnLicsIGNvbCA9ICdibGFjaycpCgojIFNQREUgcHJvamVjdG9yIG1hdHJpeCBmb3IgZXN0aW1hdGlvbi4KZnVsbF9BX2VzdCA8LSBpbmxhLnNwZGUubWFrZS5BKAogIG1hcmdpbl9tZXNoLAogIGFzLm1hdHJpeChiZWlfZGZbYmVpX2RmJGFyZWEgPiAwLCBjKCd4JywgJ3knKV0pCikKCiMgU2V0IHVwIHN0YWNrIGZvciBlc3RpbWF0aW9uLgpmdWxsX3N0YWNrX2luZGV4IDwtIGlubGEuc3BkZS5tYWtlLmluZGV4KG5hbWUgPSAnc3BhdGlhbF9maWVsZCcsIG4uc3BkZSA9IG1hcmdpbl9zcGRlJG4uc3BkZSkKZnVsbF9zdGFja19lc3QgPC0gaW5sYS5zdGFjayhkYXRhID0gbGlzdChjb3VudCA9IGJlaV9kZiRjb3VudFtiZWlfZGYkYXJlYSA+IDBdLCBsYXJlYSA9IGxvZyhiZWlfZGYkYXJlYVtiZWlfZGYkYXJlYSA+IDBdKSksIEEgPSBsaXN0KGZ1bGxfQV9lc3QpLCBlZmZlY3RzID0gbGlzdChjKGZ1bGxfc3RhY2tfaW5kZXgsIGxpc3QoaW50ZXJjZXB0ID0gMSkpKSwgdGFnID0gJ2VzdCcpCgojIFNQREUgcHJvamVjdG9yIG1hdHJpeCBmb3IgcHJlZGljdGlvbi4KZnVsbF9BX3ByZWQgPC0gaW5sYS5zcGRlLm1ha2UuQShtZXNoID0gbWFyZ2luX21lc2gsIGxvYyA9IGFzLm1hdHJpeChiZWlfZGZbLGMoJ3gnLCAneScpXSkpCgojIFNldCB1cCBzdGFja3MgZm9yIHByZWRpY3Rpb24uCmZ1bGxfc3RhY2tfbGF0ZW50IDwtIGlubGEuc3RhY2soZGF0YSA9IGxpc3QoeGkgPSBOQSksIEEgPSBsaXN0KGZ1bGxfQV9wcmVkKSwgZWZmZWN0cyA9IGxpc3QoZnVsbF9zdGFja19pbmRleCksIHRhZyA9ICdwcmVkX2xhdGVudCcpCmZ1bGxfc3RhY2tfcmVzcG9uc2UgPC0gaW5sYS5zdGFjayhkYXRhID0gbGlzdChjb3VudCA9IE5BKSwgQSA9IGxpc3QoZnVsbF9BX3ByZWQpLCBlZmZlY3RzID0gbGlzdChjKGZ1bGxfc3RhY2tfaW5kZXgsIGxpc3QoaW50ZXJjZXB0ID0gMSkpKSwgdGFnID0gJ3ByZWRfcmVzcG9uc2UnKQoKIyBKb2luIGFsbCB0aHJlZSBzdGFja3MuCmZ1bGxfc3RhY2tzIDwtIGlubGEuc3RhY2soZnVsbF9zdGFja19lc3QsIGZ1bGxfc3RhY2tfbGF0ZW50LCBmdWxsX3N0YWNrX3Jlc3BvbnNlKQoKIyBGaXQgdGhlIG1vZGVsIHdpdGggSU5MQS4Kc3lzdGVtLnRpbWUoCmJlaV9mdWxsX2ZpdCA8LSBpbmxhKAogIGNvdW50IH4gLTEgKyBpbnRlcmNlcHQgKyBmKHNwYXRpYWxfZmllbGQsIG1vZGVsID0gbWFyZ2luX3NwZGUpLAogIG9mZnNldCA9IGxhcmVhLCBmYW1pbHkgPSAncG9pc3NvbicsCiAgZGF0YSA9IGlubGEuc3RhY2suZGF0YShmdWxsX3N0YWNrcyksCiAgY29udHJvbC5wcmVkaWN0b3IgPSBsaXN0KEEgPSBpbmxhLnN0YWNrLkEoZnVsbF9zdGFja3MpLCBjb21wdXRlID0gVFJVRSksCiAgdmVyYm9zZSA9IFRSVUUKKQopCgojIE91dHB1dCBwb3N0ZXJpb3Igc3VtbWFyaWVzLgpiZWlfZnVsbF9maXQkc3VtbWFyeS5maXhlZApiZWlfZnVsbF9maXQkc3VtbWFyeS5oeXBlcnBhcgpgYGAKCmBgYHtyIGJlaWlubGFwbG90LCBjYWNoZSA9IFRSVUUsIGRlcGVuZHNvbiA9ICdiZWlpbmxhJ30KIyBQbG90IHN1cmZhY2UuCnBhcihtYXIgPSBjKDAuNSwgMCwgMiwgMikpCnBsb3QoaW0odChpbmxhLm1lc2gucHJvamVjdChwcm9qX21hcmdpbl9tZXNoLCBiZWlfZnVsbF9maXQkc3VtbWFyeS5maXhlZCRtZWFuICsgYmVpX2Z1bGxfZml0JHN1bW1hcnkucmFuZG9tJHNwYXRpYWxfZmllbGQkbWVhbikpLAogICAgICAgIHhyYW5nZSA9IEZyYW1lKGJlaSkkeCArIGMoLU1BUkdJTiwgTUFSR0lOKSwKICAgICAgICB5cmFuZ2UgPSBGcmFtZShiZWkpJHkgKyBjKC1NQVJHSU4sIE1BUkdJTiksCiAgICAgICAgdW5pdG5hbWUgPSBjKCdtZXRlcicsICdtZXRlcnMnKSksCiAgICAgICAgbWFpbiA9ICdQb3N0ZXJpb3IgTWVhbiBvZiBMb2ctSW50ZW5zaXR5JykKcGxvdChXaW5kb3coYmVpKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcG9pbnRzKGJlaSwgcGNoID0gJy4nLCBjb2wgPSAnd2hpdGUnKQpwYXIobWFyID0gYygwLjUsIDAsIDIsIDIpKQpwbG90KGltKHQoaW5sYS5tZXNoLnByb2plY3QocHJval9tYXJnaW5fbWVzaCwgc3FydChiZWlfZnVsbF9maXQkc3VtbWFyeS5maXhlZCRzZF4yICsgYmVpX2Z1bGxfZml0JHN1bW1hcnkucmFuZG9tJHNwYXRpYWxfZmllbGQkc2ReMikpKSwKICAgICAgICB4cmFuZ2UgPSBGcmFtZShiZWkpJHggKyBjKC1NQVJHSU4sIE1BUkdJTiksCiAgICAgICAgeXJhbmdlID0gRnJhbWUoYmVpKSR5ICsgYygtTUFSR0lOLCBNQVJHSU4pLAogICAgICAgIHVuaXRuYW1lID0gYygnbWV0ZXInLCAnbWV0ZXJzJykpLAogICAgICAgIG1haW4gPSAnUG9zdGVyaW9yIFNEIG9mIExvZy1JbnRlbnNpdHknKQpwbG90KFdpbmRvdyhiZWkpLCBib3JkZXIgPSAnd2hpdGUnLCBhZGQgPSBUUlVFKQpwb2ludHMoYmVpLCBwY2ggPSAnLicsIGNvbCA9ICd3aGl0ZScpCmBgYAoKYGBge3IgYmVpaG9sZWlubGEsIGNhY2hlID0gVFJVRSwgZGVwZW5kc29uID0gJ2VmZm9ydCd9CmJlaV9ob2xlX2RmIDwtIGRhdGEuZnJhbWUoeCA9IGNlbnRlcnMkeCwgeSA9IGNlbnRlcnMkeSwKICAgICAgICAgICAgICAgICAgICAgICAgICBjb3VudCA9IE5BX2ludGVnZXJfLCBhcmVhID0gTkFfcmVhbF8pCgpzeXN0ZW0udGltZSgKZm9yKHIgaW4gc2VxX2xlbihucm93KGJlaV9ob2xlX2RmKSkpewogIGJlaV9ob2xlX2RmJGNvdW50W3JdIDwtIHN1bShiZWlfaG9sZSR4ID49IGJlaV9ob2xlX2RmJHhbcl0gLSBkeCAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlaV9ob2xlJHggPCBiZWlfaG9sZV9kZiR4W3JdICsgZHggJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZWlfaG9sZSR5ID49IGJlaV9ob2xlX2RmJHlbcl0gLSBkeSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlaV9ob2xlJHkgPCBiZWlfaG9sZV9kZiR5W3JdICsgZHkpCiAgYmVpX2hvbGVfZGYkYXJlYVtyXSA8LSBhcmVhKFdpbmRvdyhiZWlfaG9sZSlbb3dpbihjKGJlaV9ob2xlX2RmJHhbcl0gLSBkeCwgYmVpX2hvbGVfZGYkeFtyXSArIGR4KSwgYyhiZWlfaG9sZV9kZiR5W3JdIC0gZHksIGJlaV9ob2xlX2RmJHlbcl0gKyBkeSkpXSkKfQopCgpwYXIobWFyID0gYygwLjUsIDAsIDIsIDIpKQpwbG90KGltKHQobWF0cml4KGlmZWxzZShiZWlfaG9sZV9kZiRhcmVhID4gMCwgYmVpX2hvbGVfZGYkY291bnQsIE5BKSwgbnJvdyA9IGxlbmd0aCh1bmlxdWUoYmVpX2hvbGVfZGYkeCkpKSksIHVuaXF1ZShiZWlfaG9sZV9kZiR4KSwgdW5pcXVlKGJlaV9ob2xlX2RmJHkpLCB1bml0bmFtZSA9ICdtZXRlcnMnKSwgbmNvbGNvdXJzID0gcmFuZ2UoYmVpX2hvbGVfZGYkY291bnQpICUqJSBjKC0xLCAxKSArIDEsIG1haW4gPSAnQmlubmVkIFRyZWUgQ291bnRzJykKcGxvdChXaW5kb3coYmVpX2hvbGUpLCBib3JkZXIgPSAnd2hpdGUnLCBhZGQgPSBUUlVFKQpwb2ludHMoYmVpX2hvbGUsIHBjaCA9ICcuJywgY29sID0gJyMwMDAwMDA0MCcpCgojIFNQREUgcHJvamVjdG9yIG1hdHJpeCBmb3IgZXN0aW1hdGlvbi4KaG9sZV9BX2VzdCA8LSBpbmxhLnNwZGUubWFrZS5BKAogIG1hcmdpbl9ob2xlLAogIGFzLm1hdHJpeChiZWlfaG9sZV9kZltiZWlfaG9sZV9kZiRhcmVhID4gMCwgYygneCcsICd5JyldKQopCgojIFNldCB1cCBzdGFjayBmb3IgZXN0aW1hdGlvbi4KaG9sZV9zdGFja19pbmRleCA8LSBpbmxhLnNwZGUubWFrZS5pbmRleChuYW1lID0gJ3NwYXRpYWxfZmllbGQnLCBuLnNwZGUgPSBtYXJnaW5faG9sZV9zcGRlJG4uc3BkZSkKaG9sZV9zdGFja19lc3QgPC0gaW5sYS5zdGFjayhkYXRhID0gbGlzdChjb3VudCA9IGJlaV9ob2xlX2RmJGNvdW50W2JlaV9ob2xlX2RmJGFyZWEgPiAwXSwgbGFyZWEgPSBsb2coYmVpX2hvbGVfZGYkYXJlYVtiZWlfaG9sZV9kZiRhcmVhID4gMF0pKSwgQSA9IGxpc3QoaG9sZV9BX2VzdCksIGVmZmVjdHMgPSBsaXN0KGMoaG9sZV9zdGFja19pbmRleCwgbGlzdChpbnRlcmNlcHQgPSAxKSkpLCB0YWcgPSAnZXN0JykKCiMgU1BERSBwcm9qZWN0b3IgbWF0cml4IGZvciBwcmVkaWN0aW9uLgpob2xlX0FfcHJlZCA8LSBpbmxhLnNwZGUubWFrZS5BKG1lc2ggPSBtYXJnaW5faG9sZSwgbG9jID0gYXMubWF0cml4KGJlaV9ob2xlX2RmWyxjKCd4JywgJ3knKV0pKQoKIyBTZXQgdXAgc3RhY2tzIGZvciBwcmVkaWN0aW9uLgpob2xlX3N0YWNrX2xhdGVudCA8LSBpbmxhLnN0YWNrKGRhdGEgPSBsaXN0KHhpID0gTkEpLCBBID0gbGlzdChob2xlX0FfcHJlZCksIGVmZmVjdHMgPSBsaXN0KGhvbGVfc3RhY2tfaW5kZXgpLCB0YWcgPSAncHJlZF9sYXRlbnQnKQpob2xlX3N0YWNrX3Jlc3BvbnNlIDwtIGlubGEuc3RhY2soZGF0YSA9IGxpc3QoY291bnQgPSBOQSksIEEgPSBsaXN0KGhvbGVfQV9wcmVkKSwgZWZmZWN0cyA9IGxpc3QoYyhob2xlX3N0YWNrX2luZGV4LCBsaXN0KGludGVyY2VwdCA9IDEpKSksIHRhZyA9ICdwcmVkX3Jlc3BvbnNlJykKCiMgSm9pbiBhbGwgdGhyZWUgc3RhY2tzLgpob2xlX3N0YWNrcyA8LSBpbmxhLnN0YWNrKGhvbGVfc3RhY2tfZXN0LCBob2xlX3N0YWNrX2xhdGVudCwgaG9sZV9zdGFja19yZXNwb25zZSkKCiMgRml0IHRoZSBtb2RlbCB3aXRoIElOTEEuCnN5c3RlbS50aW1lKApiZWlfaG9sZV9maXQgPC0gaW5sYSgKICBjb3VudCB+IC0xICsgaW50ZXJjZXB0ICsgZihzcGF0aWFsX2ZpZWxkLCBtb2RlbCA9IG1hcmdpbl9ob2xlX3NwZGUpLAogIG9mZnNldCA9IGxhcmVhLCBmYW1pbHkgPSAncG9pc3NvbicsCiAgZGF0YSA9IGlubGEuc3RhY2suZGF0YShob2xlX3N0YWNrcyksCiAgY29udHJvbC5wcmVkaWN0b3IgPSBsaXN0KEEgPSBpbmxhLnN0YWNrLkEoaG9sZV9zdGFja3MpLCBjb21wdXRlID0gVFJVRSksCiAgdmVyYm9zZSA9IFRSVUUKKQopCgojIE91dHB1dCBwb3N0ZXJpb3Igc3VtbWFyaWVzLgpiZWlfaG9sZV9maXQkc3VtbWFyeS5maXhlZApiZWlfaG9sZV9maXQkc3VtbWFyeS5oeXBlcnBhcgpgYGAKCmBgYHtyIGJlaWhvbGVpbmxhcGxvdCwgY2FjaGUgPSBUUlVFLCBkZXBlbmRzb24gPSAnYmVpaG9sZWlubGEnfQojIFBsb3Qgc3VyZmFjZS4KcGFyKG1hciA9IGMoMC41LCAwLCAyLCAyKSkKcGxvdChpbSh0KGlubGEubWVzaC5wcm9qZWN0KHByb2pfbWFyZ2luX2hvbGUsIGJlaV9ob2xlX2ZpdCRzdW1tYXJ5LmZpeGVkJG1lYW4gKyBiZWlfaG9sZV9maXQkc3VtbWFyeS5yYW5kb20kc3BhdGlhbF9maWVsZCRtZWFuKSksCiAgICAgICAgeHJhbmdlID0gRnJhbWUoYmVpKSR4ICsgYygtTUFSR0lOLCBNQVJHSU4pLAogICAgICAgIHlyYW5nZSA9IEZyYW1lKGJlaSkkeSArIGMoLU1BUkdJTiwgTUFSR0lOKSwKICAgICAgICB1bml0bmFtZSA9IGMoJ21ldGVyJywgJ21ldGVycycpKSwKICAgICAgICBtYWluID0gJ1Bvc3RlcmlvciBNZWFuIG9mIExvZy1JbnRlbnNpdHknKQpwbG90KFdpbmRvdyhiZWlfaG9sZSksIGJvcmRlciA9ICd3aGl0ZScsIGFkZCA9IFRSVUUpCnBvaW50cyhiZWlfaG9sZSwgcGNoID0gJy4nLCBjb2wgPSAnd2hpdGUnKQpwYXIobWFyID0gYygwLjUsIDAsIDIsIDIpKQpwbG90KGltKHQoaW5sYS5tZXNoLnByb2plY3QocHJval9tYXJnaW5faG9sZSwgc3FydChiZWlfaG9sZV9maXQkc3VtbWFyeS5maXhlZCRzZF4yICsgYmVpX2hvbGVfZml0JHN1bW1hcnkucmFuZG9tJHNwYXRpYWxfZmllbGQkc2ReMikpKSwKICAgICAgICB4cmFuZ2UgPSBGcmFtZShiZWkpJHggKyBjKC1NQVJHSU4sIE1BUkdJTiksCiAgICAgICAgeXJhbmdlID0gRnJhbWUoYmVpKSR5ICsgYygtTUFSR0lOLCBNQVJHSU4pLAogICAgICAgIHVuaXRuYW1lID0gYygnbWV0ZXInLCAnbWV0ZXJzJykpLAogICAgICAgIG1haW4gPSAnUG9zdGVyaW9yIFNEIG9mIExvZy1JbnRlbnNpdHknKQpwbG90KFdpbmRvdyhiZWlfaG9sZSksIGJvcmRlciA9ICd3aGl0ZScsIGFkZCA9IFRSVUUpCnBvaW50cyhiZWlfaG9sZSwgcGNoID0gJy4nLCBjb2wgPSAnd2hpdGUnKQpgYGAKCmBgYHtyIGJlaXNhbXBpbmxhLCBjYWNoZSA9IFRSVUUsIGRlcGVuZHNvbiA9ICdlZmZvcnQnfQpiZWlfc2FtcF9kZiA8LSBkYXRhLmZyYW1lKHggPSBjZW50ZXJzJHgsIHkgPSBjZW50ZXJzJHksCiAgICAgICAgICAgICAgICAgICAgICAgICAgY291bnQgPSBOQV9pbnRlZ2VyXywgYXJlYSA9IE5BX3JlYWxfKQoKc3lzdGVtLnRpbWUoCmZvcihyIGluIHNlcV9sZW4obnJvdyhiZWlfc2FtcF9kZikpKXsKICBiZWlfc2FtcF9kZiRjb3VudFtyXSA8LSBzdW0oYmVpX3NhbXAkeCA+PSBiZWlfc2FtcF9kZiR4W3JdIC0gZHggJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZWlfc2FtcCR4IDwgYmVpX3NhbXBfZGYkeFtyXSArIGR4ICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmVpX3NhbXAkeSA+PSBiZWlfc2FtcF9kZiR5W3JdIC0gZHkgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZWlfc2FtcCR5IDwgYmVpX3NhbXBfZGYkeVtyXSArIGR5KQogIGJlaV9zYW1wX2RmJGFyZWFbcl0gPC0gYXJlYShXaW5kb3coYmVpX3NhbXApW293aW4oYyhiZWlfc2FtcF9kZiR4W3JdIC0gZHgsIGJlaV9zYW1wX2RmJHhbcl0gKyBkeCksIGMoYmVpX3NhbXBfZGYkeVtyXSAtIGR5LCBiZWlfc2FtcF9kZiR5W3JdICsgZHkpKV0pCn0KKQoKcGFyKG1hciA9IGMoMC41LCAwLCAyLCAyKSkKcGxvdChpbSh0KG1hdHJpeChpZmVsc2UoYmVpX3NhbXBfZGYkYXJlYSA+IDAsIGJlaV9zYW1wX2RmJGNvdW50LCBOQSksIG5yb3cgPSBsZW5ndGgodW5pcXVlKGJlaV9zYW1wX2RmJHgpKSkpLCB1bmlxdWUoYmVpX3NhbXBfZGYkeCksIHVuaXF1ZShiZWlfc2FtcF9kZiR5KSwgdW5pdG5hbWUgPSAnbWV0ZXJzJyksIG5jb2xjb3VycyA9IHJhbmdlKGJlaV9zYW1wX2RmJGNvdW50KSAlKiUgYygtMSwgMSkgKyAxLCBtYWluID0gJ0Jpbm5lZCBUcmVlIENvdW50cycpCnBsb3QoV2luZG93KGJlaV9zYW1wKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcG9pbnRzKGJlaV9zYW1wLCBwY2ggPSAnLicsIGNvbCA9ICcjMDAwMDAwNDAnKQoKIyBTUERFIHByb2plY3RvciBtYXRyaXggZm9yIGVzdGltYXRpb24uCnNhbXBfQV9lc3QgPC0gaW5sYS5zcGRlLm1ha2UuQSgKICBtYXJnaW5fc2FtcCwKICBhcy5tYXRyaXgoYmVpX3NhbXBfZGZbYmVpX3NhbXBfZGYkYXJlYSA+IDAsIGMoJ3gnLCAneScpXSkKKQoKIyBTZXQgdXAgc3RhY2sgZm9yIGVzdGltYXRpb24uCnNhbXBfc3RhY2tfaW5kZXggPC0gaW5sYS5zcGRlLm1ha2UuaW5kZXgobmFtZSA9ICdzcGF0aWFsX2ZpZWxkJywgbi5zcGRlID0gbWFyZ2luX3NhbXBfc3BkZSRuLnNwZGUpCnNhbXBfc3RhY2tfZXN0IDwtIGlubGEuc3RhY2soZGF0YSA9IGxpc3QoY291bnQgPSBiZWlfc2FtcF9kZiRjb3VudFtiZWlfc2FtcF9kZiRhcmVhID4gMF0sIGxhcmVhID0gbG9nKGJlaV9zYW1wX2RmJGFyZWFbYmVpX3NhbXBfZGYkYXJlYSA+IDBdKSksIEEgPSBsaXN0KHNhbXBfQV9lc3QpLCBlZmZlY3RzID0gbGlzdChjKHNhbXBfc3RhY2tfaW5kZXgsIGxpc3QoaW50ZXJjZXB0ID0gMSkpKSwgdGFnID0gJ2VzdCcpCgojIFNQREUgcHJvamVjdG9yIG1hdHJpeCBmb3IgcHJlZGljdGlvbi4Kc2FtcF9BX3ByZWQgPC0gaW5sYS5zcGRlLm1ha2UuQShtZXNoID0gbWFyZ2luX3NhbXAsIGxvYyA9IGFzLm1hdHJpeChiZWlfc2FtcF9kZlssYygneCcsICd5JyldKSkKCiMgU2V0IHVwIHN0YWNrcyBmb3IgcHJlZGljdGlvbi4Kc2FtcF9zdGFja19sYXRlbnQgPC0gaW5sYS5zdGFjayhkYXRhID0gbGlzdCh4aSA9IE5BKSwgQSA9IGxpc3Qoc2FtcF9BX3ByZWQpLCBlZmZlY3RzID0gbGlzdChzYW1wX3N0YWNrX2luZGV4KSwgdGFnID0gJ3ByZWRfbGF0ZW50JykKc2FtcF9zdGFja19yZXNwb25zZSA8LSBpbmxhLnN0YWNrKGRhdGEgPSBsaXN0KGNvdW50ID0gTkEpLCBBID0gbGlzdChzYW1wX0FfcHJlZCksIGVmZmVjdHMgPSBsaXN0KGMoc2FtcF9zdGFja19pbmRleCwgbGlzdChpbnRlcmNlcHQgPSAxKSkpLCB0YWcgPSAncHJlZF9yZXNwb25zZScpCgojIEpvaW4gYWxsIHRocmVlIHN0YWNrcy4Kc2FtcF9zdGFja3MgPC0gaW5sYS5zdGFjayhzYW1wX3N0YWNrX2VzdCwgc2FtcF9zdGFja19sYXRlbnQsIHNhbXBfc3RhY2tfcmVzcG9uc2UpCgojIEZpdCB0aGUgbW9kZWwgd2l0aCBJTkxBLgpzeXN0ZW0udGltZSgKYmVpX3NhbXBfZml0IDwtIGlubGEoCiAgY291bnQgfiAtMSArIGludGVyY2VwdCArIGYoc3BhdGlhbF9maWVsZCwgbW9kZWwgPSBtYXJnaW5fc2FtcF9zcGRlKSwKICBvZmZzZXQgPSBsYXJlYSwgZmFtaWx5ID0gJ3BvaXNzb24nLAogIGRhdGEgPSBpbmxhLnN0YWNrLmRhdGEoc2FtcF9zdGFja3MpLAogIGNvbnRyb2wucHJlZGljdG9yID0gbGlzdChBID0gaW5sYS5zdGFjay5BKHNhbXBfc3RhY2tzKSwgY29tcHV0ZSA9IFRSVUUpLAogIHZlcmJvc2UgPSBUUlVFCikKKQoKIyBPdXRwdXQgcG9zdGVyaW9yIHN1bW1hcmllcy4KYmVpX3NhbXBfZml0JHN1bW1hcnkuZml4ZWQKYmVpX3NhbXBfZml0JHN1bW1hcnkuaHlwZXJwYXIKYGBgCgpgYGB7ciBiZWlzYW1waW5sYXBsb3QsIGNhY2hlID0gVFJVRSwgZGVwZW5kc29uID0gJ2JlaXNhbXBpbmxhJ30KIyBQbG90IHN1cmZhY2UuCnBhcihtYXIgPSBjKDAuNSwgMCwgMiwgMikpCnBsb3QoaW0odChpbmxhLm1lc2gucHJvamVjdChwcm9qX21hcmdpbl9zYW1wLCBiZWlfc2FtcF9maXQkc3VtbWFyeS5maXhlZCRtZWFuICsgYmVpX3NhbXBfZml0JHN1bW1hcnkucmFuZG9tJHNwYXRpYWxfZmllbGQkbWVhbikpLAogICAgICAgIHhyYW5nZSA9IEZyYW1lKGJlaSkkeCArIGMoLU1BUkdJTiwgTUFSR0lOKSwKICAgICAgICB5cmFuZ2UgPSBGcmFtZShiZWkpJHkgKyBjKC1NQVJHSU4sIE1BUkdJTiksCiAgICAgICAgdW5pdG5hbWUgPSBjKCdtZXRlcicsICdtZXRlcnMnKSksCiAgICAgICAgbWFpbiA9ICdQb3N0ZXJpb3IgTWVhbiBvZiBMb2ctSW50ZW5zaXR5JykKcGxvdChXaW5kb3coYmVpKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcGxvdChXaW5kb3coYmVpX3NhbXApLCBib3JkZXIgPSAnd2hpdGUnLCBhZGQgPSBUUlVFKQpwb2ludHMoYmVpX3NhbXAsIHBjaCA9ICcuJywgY29sID0gJ3doaXRlJykKcGFyKG1hciA9IGMoMC41LCAwLCAyLCAyKSkKcGxvdChpbSh0KGlubGEubWVzaC5wcm9qZWN0KHByb2pfbWFyZ2luX3NhbXAsIHNxcnQoYmVpX3NhbXBfZml0JHN1bW1hcnkuZml4ZWQkc2ReMiArIGJlaV9zYW1wX2ZpdCRzdW1tYXJ5LnJhbmRvbSRzcGF0aWFsX2ZpZWxkJHNkXjIpKSksCiAgICAgICAgeHJhbmdlID0gRnJhbWUoYmVpKSR4ICsgYygtTUFSR0lOLCBNQVJHSU4pLAogICAgICAgIHlyYW5nZSA9IEZyYW1lKGJlaSkkeSArIGMoLU1BUkdJTiwgTUFSR0lOKSwKICAgICAgICB1bml0bmFtZSA9IGMoJ21ldGVyJywgJ21ldGVycycpKSwKICAgICAgICBtYWluID0gJ1Bvc3RlcmlvciBTRCBvZiBMb2ctSW50ZW5zaXR5JykKcGxvdChXaW5kb3coYmVpKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcGxvdChXaW5kb3coYmVpX3NhbXApLCBib3JkZXIgPSAnd2hpdGUnLCBhZGQgPSBUUlVFKQpwb2ludHMoYmVpX3NhbXAsIHBjaCA9ICcuJywgY29sID0gJ3doaXRlJykKYGBgCgoKIyBCZWkgRGF0YXNldCB3aXRoIEBzaW1wc29uZXRhbCBtZXRob2QKClRoaXMgbWV0aG9kIHJlbGllcyB1cG9uIHRoZSBAbGluZGdyZW5ldGFsIGFwcHJveGltYXRpb24gb2YgdGhlIGxhdGVudCBHYXVzc2lhbgpmaWVsZCBhcyBhIGxpbmVhciBjb21iaW5hdGlvbiBvZiBhIGZpbml0ZSBudW1iZXIgb2YgYmFzaXMgZnVuY3Rpb25zIHJlcHJlc2VudGVkCmFzIGEgR01SRiBvbiB0aGUgbm9kZXMgb2YgYSB0cmlhbmd1bGF0aW9uIG9mIHRoZSBzcGFjZS4gQHNpbXBzb25ldGFsIHVzZSB0aGUKdHJpYW5ndWxhdGlvbiBmb3IgbnVtZXJpY2FsIGludGVncmF0aW9uIG9mIHRoZSBpbnRlbnNpdHkgZnVuY3Rpb24gYW5kIHNob3cgdGhhdAp0aGUgTEdDUCBsaWtlbGlob29kIGZhY3RvcnMgaW50byB0aGUgam9pbnQgZGlzdHJpYnV0aW9uIG9mIGluZGVwZW5kZW50IFBvaXNzb24KcmFuZG9tIHZhcmlhYmxlcyBjb3JyZXNwb25kaW5nIHRvIHRoZSBwb2ludHMgb2YgdGhlIHBvaW50IHBhdHRlcm4gYW5kIHRoZSBub2RlcwpvZiB0aGUgdHJpYW5ndWxhdGlvbi4gVGhlIG1vZGVsIGZpdHRpbmcgcHJvY2VlZHMgdXNpbmcgSU5MQSB0byBmaXQgYSBQb2lzc29uCm1vZGVsIHRvIHBzZXVkb2RhdGEuCgpUaGUgcHNldWRvZGF0YSBhcmUgY29uc3RydWN0ZWQgYXMgZm9sbG93cy4KCi0gTGV0ICRuJCBiZSB0aGUgc2l6ZSBvZiB0aGUgcG9pbnQgcGF0dGVybi4KLSBMZXQgJHAkIGJlIHRoZSBudW1iZXIgb2Ygbm9kZXMgb2YgdHJpYW5ndWxhdGlvbi4KLSBEZWZpbmUgdGhlIHBzZXVkb2RhdGEgYXMKICAkXG1hdGhiZnt5fSA9ICh5X3sxfSwgXGRvdHMsIHlfe3B9LCB5X3twKzF9LCBcZG90cywgeV97cCtufSknJCB3aGVyZQogICR5X3tpfSA9IDAkIGZvciAkaSA9IDEsIFxkb3RzLCBwJCAodGhlIHRyYWluZ3VsYXRpb24gbm9kZXMpIGFuZCAkeV97aX0gPSAxJAogIGZvciAkaSA9IHArMSwgXGRvdHMsIHArbiQgKHRoZSBvYnNlcnZlZCBwb2ludHMpLgotIERlZmluZSAkXGJvbGRzeW1ib2x7XGFscGhhfSA9IChcYWxwaGFfe2l9LCBcZG90cywgXGFscGhhX3twfSwKICBcYWxwaGFfe3ArMX0sIFxkb3RzLCBcYWxwaGFfe3Arbn0pJyQgdG8gZW5jb2RlIHRoZSBudW1lcmljYWwgaW50ZWdyYXRpb24KICBzY2hlbWUgd2hlcmUgJFxhbHBoYV97aX0kIGlzIHRoZSBudW1lcmljYWwgaW50ZWdyYXRpb24gd2VpZ2h0IGZvcgogICRpID0gMSwgXGRvdHMsIHAkICh0aGUgdHJhaW5ndWxhdGlvbiBub2RlcykgYW5kICRcYWxwaGFfe2l9ID0gMCQgICBmb3IKICAkaSA9IHArMSwgXGRvdHMsIHArbiQgKHRoZSBvYnNlcnZlZCBwb2ludHMpLgoKVGhlbiAkeV97aX0gXHNpbSBQb2lzc29uKFxhbHBoYV97aX1cZXRhX3tpfSkkIHdoZXJlICRcbG9nKFxldGFfe2l9KSQgaXMgdGhlClNQREUgcmVwcmVzZW50YXRpb24gb2YgdGhlIEdGIGF0IHRoZSBsb2NhdGlvbiBvZiB0aGUgJGkkdGggcHNldWRvZGF0dW0uIFNlZQp0aGUgcGFwZXIgZm9yIHRlZGlvdXMgbm90YXRpb24gcmVnYXJkaW5nIHRoZSBkZWZpbml0aW9uIG9mICRcZXRhX3tpfSQuClVsdGltYXRlbHksIHRoZSBub2RlcyBiZWNvbWUgUG9pc3NvbiByYW5kb20gdmFyaWFibGVzIHdpdGggbWVhbnMgZXF1YWwgdG8gdGhlaXIKcmVzcGVjdGl2ZSBpbnRlZ3JhdGlvbiB3ZWlnaHRzIHRpbWVzIHRoZSBpbnRlbnNpdHkgYXQgdGhhdCB0aGVpciBsb2NhdGlvbnMKKHNvIHRoZSBpbnRlZ3JhdGlvbiB3ZWlnaHQgaXMgYW4gZXhwb3N1cmUgdmFyaWFibGUpLCBvYnNlcnZlZCBwb2ludHMgYmVjb21lClBvaXNzb24gcmFuZG9tIHZhcmlhYmxlcyB3aXRoIG1lYW5zIG9mIDEsIGFuZCB0aGUgbGlrZWxpaG9vZCBpcyBhcHByb3hpbWF0ZWx5CnByb3BvcnRpb25hbCB0bwoKJCRccHJvZF97aT0xfV57cCtufSBcZXRhX3tpfV57eV97aX19IFxleHAoLVxhbHBoYV97aX0gXGV0YV97aX0pLiQkCgpfKElzIHRoZXJlIGEgbWlzc2luZ18gJFxhbHBoYV97aX0kXz8pXwoKQ292YXJpYXRlcyBjYW4gZWFzaWx5IGJlIGluY2x1ZGVkIHdoZW4gdGhleSBhcmUgb2JzZXJ2ZWQgYXQgdGhlIG1lc2ggbm9kZXMuCktub3duICJzYW1wbGluZyBlZmZvcnQiIGlzIGFjY29tb2RhdGVkIGJ5IHNjYWxpbmcgdGhlIGludGVncmF0aW9uIHdlaWdodHMgYnkKdGhlIHByb2JhYmlsaXR5IHRoYXQgYSBwb2ludCB3b3VsZCBoYXZlIGJlZW4gb2JzZXJ2ZWQgYXQgdGhhdCBub2RlIChnaXZlbiB0aGUKc2FtcGxpbmcgcGxhbiksIGkuZS4gbm9kZXMgaW4gb2JzZXJ2ZWQgcmVnaW9ucyBoYXZlIHRoZSAkXGFscGhhX3tpfSQgZGVmaW5lZAphYm92ZSBhbmQgbm9kZXMgaW4gdW5vYnNlcnZlZCByZWdpb25zIGhhdmUgJFxhbHBoYV97aX0gPSAwJC4gV2VpZ2h0cyBjYW4gYmUKc2NhbGVkIGJ5IHZhbHVlcyBvdGhlciB0aGFuIDAgb3IgMSB0byBhY2NvdW50IGZvciBlLmcuIGRpc3RhbmNlIHNhbXBsaW5nIHdpdGgKYSAoa25vd24pIGRldGVjdGlvbiBmdW5jdGlvbiBvciBhIChrbm93bikgZmFsc2UgbmVnYXRpdmUgcmF0ZSBmb3IgZGV0ZWN0aW9uCmVxdWlwbWVudC4gVGhpcyBhZGp1c3RtZW50IGFzc3VtZXMgdGhlIHBvaW50IHByb2Nlc3Mgb2YgaW50ZXJlc3QgaXMgb2JzZXJ2ZWQKdGhyb3VnaCBhIGtub3duIHRoaW5uaW5nIHByb2Nlc3MgYW5kIHRoZW4gYWxsb3dzIGluZmVyZW5jZSBiYWNrIHRvIHRoZQppbnRlbnNpdHkgZnVuY3Rpb24gb2YgdGhlIHVudGhpbm5lZCBwcm9jZXNzLiBNb3JlIGNvbXBsaWNhdGVkIGRldGVjdGlvbgpwcm9jZXNzZXMgYXJlIHBvc3NpYmxlLCBlLmcuIEB5dWFuZXRhbCBmaXQgYW4gTEdDUCB0byBkYXRhIG9idGlhbmVkIHRocm91Z2gKZGlzdGFuY2Ugc2FtcGxpbmcgd2hpbGUgdXNpbmcgc3BsaW5lcyB0byBtb2RlbCB0aGUgZGV0ZWN0aW9uIGZ1bmN0aW9uLgoKYGBge3IgYmVpbm9ncmlkZnVsbCwgY2FjaGUgPSBUUlVFLCBkZXBlbmRzb24gPSAnZWZmb3J0J30KZnVsbF9wdHMgPC0gY2JpbmQoYmVpJHgsIGJlaSR5KQoKIyBDb250cnVjdCB0aGUgU1BERSBBIG1hdHJpeCBmb3Igbm9kZXMgYW5kIHBvaW50cy4KZnVsbF9uViA8LSBtYXJnaW5fbWVzaCRuCmZ1bGxfbkRhdGEgPC0gZGltKGZ1bGxfcHRzKVsxXQpmdWxsX0xvY2F0aW9uTWF0cml4IDwtIGlubGEubWVzaC5wcm9qZWN0KG1hcmdpbl9tZXNoLCBmdWxsX3B0cykkQQpmdWxsX0ludGVncmF0aW9uTWF0cml4IDwtIHNwYXJzZU1hdHJpeChpID0gMTpmdWxsX25WLCBqID0gMTpmdWxsX25WLCB4ID0gcmVwKDEsIGZ1bGxfblYpKQpmdWxsX09ic2VydmF0aW9uTWF0cml4IDwtIHJiaW5kKGZ1bGxfSW50ZWdyYXRpb25NYXRyaXgsIGZ1bGxfTG9jYXRpb25NYXRyaXgpCgojIEdldCB0aGUgaW50ZWdyYXRpb24gd2VpZ2h0cy4KZnVsbF9JbnRlZ3JhdGlvbldlaWdodHMgPC0gZGlhZyhpbmxhLm1lc2guZmVtKG1hcmdpbl9tZXNoKSRjMCkKZnVsbF9FX3BvaW50X3Byb2Nlc3MgPC0gYyhvYnNfZnVsbCAqIGZ1bGxfSW50ZWdyYXRpb25XZWlnaHRzLCByZXAoMCwgZnVsbF9uRGF0YSkpCgojIENyZWF0ZSB0aGUgcHN1ZWRvZGF0YS4KZnVsbF9mYWtlX2RhdGEgPC0gYyhyZXAoMCwgZnVsbF9uViksIHJlcCgxLCBmdWxsX25EYXRhKSkKCiMgRml0IG1vZGVsIHRvIGZ1bGwgc2l0ZS4KZnVsbF9mb3JtdWxhIDwtIHkgfiAtMSArIGludGVyY2VwdCArIGYoaWR4LCBtb2RlbCA9IG1hcmdpbl9zcGRlKSAjIE5vIGNvdmFyaWF0ZXMuCmZ1bGxfZGF0YSA8LSBsaXN0KHkgPSBmdWxsX2Zha2VfZGF0YSwgaWR4ID0gMTpmdWxsX25WLCBpbnRlcmNlcHQgPSByZXAoMSwgZnVsbF9uVikpCgpzeXN0ZW0udGltZSgKcmVzdWx0X2Z1bGwgPC0gaW5sYSgKICBmb3JtdWxhID0gZnVsbF9mb3JtdWxhLAogIGRhdGEgPSBmdWxsX2RhdGEsCiAgZmFtaWx5ID0gJ3BvaXNzb24nLAogIGNvbnRyb2wucHJlZGljdG9yID0gbGlzdChBID0gZnVsbF9PYnNlcnZhdGlvbk1hdHJpeCksCiAgRSA9IGZ1bGxfRV9wb2ludF9wcm9jZXNzLAogIHZlcmJvc2UgPSBUUlVFCikKKQoKcmVzdWx0X2Z1bGwkc3VtbWFyeS5maXhlZApyZXN1bHRfZnVsbCRzdW1tYXJ5Lmh5cGVycGFyCmBgYAoKYGBge3IgYmVpbm9ncmlkZnVsbHBsb3QsIGNhY2hlID0gVFJVRSwgZGVwZW5kc29uID0gJ2JlaW5vZ3JpZGZ1bGwnfQojIFBsb3Qgc3VyZmFjZS4KcGFyKG1hciA9IGMoMC41LCAwLCAyLCAyKSkKcGxvdChpbSh0KGlubGEubWVzaC5wcm9qZWN0KHByb2pfbWFyZ2luX21lc2gsIHJlc3VsdF9mdWxsJHN1bW1hcnkuZml4ZWQkbWVhbiArIHJlc3VsdF9mdWxsJHN1bW1hcnkucmFuZG9tJGlkeCRtZWFuKSksCiAgICAgICAgeHJhbmdlID0gRnJhbWUoYmVpKSR4ICsgYygtTUFSR0lOLCBNQVJHSU4pLAogICAgICAgIHlyYW5nZSA9IEZyYW1lKGJlaSkkeSArIGMoLU1BUkdJTiwgTUFSR0lOKSwKICAgICAgICB1bml0bmFtZSA9IGMoJ21ldGVyJywgJ21ldGVycycpKSwKICAgICAgICBtYWluID0gJ1Bvc3RlcmlvciBNZWFuIG9mIExvZy1JbnRlbnNpdHknKQpwbG90KFdpbmRvdyhiZWkpLCBib3JkZXIgPSAnd2hpdGUnLCBhZGQgPSBUUlVFKQpwb2ludHMoYmVpLCBwY2ggPSAnLicsIGNvbCA9ICd3aGl0ZScpCnBhcihtYXIgPSBjKDAuNSwgMCwgMiwgMikpCnBsb3QoaW0odChpbmxhLm1lc2gucHJvamVjdChwcm9qX21hcmdpbl9tZXNoLCBzcXJ0KHJlc3VsdF9mdWxsJHN1bW1hcnkuZml4ZWQkc2ReMiArIHJlc3VsdF9mdWxsJHN1bW1hcnkucmFuZG9tJGlkeCRzZF4yKSkpLAogICAgICAgIHhyYW5nZSA9IEZyYW1lKGJlaSkkeCArIGMoLU1BUkdJTiwgTUFSR0lOKSwKICAgICAgICB5cmFuZ2UgPSBGcmFtZShiZWkpJHkgKyBjKC1NQVJHSU4sIE1BUkdJTiksCiAgICAgICAgdW5pdG5hbWUgPSBjKCdtZXRlcicsICdtZXRlcnMnKSksCiAgICAgICAgbWFpbiA9ICdQb3N0ZXJpb3IgU0Qgb2YgTG9nLUludGVuc2l0eScpCnBsb3QoV2luZG93KGJlaSksIGJvcmRlciA9ICd3aGl0ZScsIGFkZCA9IFRSVUUpCnBvaW50cyhiZWksIHBjaCA9ICcuJywgY29sID0gJ3doaXRlJykKYGBgCgpgYGB7ciBiZWlub2dyaWRob2xlLCBjYWNoZSA9IFRSVUUsIGRlcGVuZHNvbiA9ICdlZmZvcnQnfQpob2xlX3B0cyA8LSBjYmluZChiZWlfaG9sZSR4LCBiZWlfaG9sZSR5KQoKIyBDb250cnVjdCB0aGUgU1BERSBBIG1hdHJpeCBmb3Igbm9kZXMgYW5kIHBvaW50cy4KaG9sZV9uViA8LSBtYXJnaW5faG9sZSRuCmhvbGVfbkRhdGEgPC0gZGltKGhvbGVfcHRzKVsxXQpob2xlX0xvY2F0aW9uTWF0cml4IDwtIGlubGEubWVzaC5wcm9qZWN0KG1hcmdpbl9ob2xlLCBob2xlX3B0cykkQQpob2xlX0ludGVncmF0aW9uTWF0cml4IDwtIHNwYXJzZU1hdHJpeChpID0gMTpob2xlX25WLCBqID0gMTpob2xlX25WLCB4ID0gcmVwKDEsIGhvbGVfblYpKQpob2xlX09ic2VydmF0aW9uTWF0cml4IDwtIHJiaW5kKGhvbGVfSW50ZWdyYXRpb25NYXRyaXgsIGhvbGVfTG9jYXRpb25NYXRyaXgpCgojIEdldCB0aGUgaW50ZWdyYXRpb24gd2VpZ2h0cy4KaG9sZV9JbnRlZ3JhdGlvbldlaWdodHMgPC0gZGlhZyhpbmxhLm1lc2guZmVtKG1hcmdpbl9ob2xlKSRjMCkKaG9sZV9FX3BvaW50X3Byb2Nlc3MgPC0gYyhvYnNfaG9sZSAqIGhvbGVfSW50ZWdyYXRpb25XZWlnaHRzLCByZXAoMCwgaG9sZV9uRGF0YSkpCgojIENyZWF0ZSB0aGUgcHN1ZWRvZGF0YS4KaG9sZV9mYWtlX2RhdGEgPC0gYyhyZXAoMCwgaG9sZV9uViksIHJlcCgxLCBob2xlX25EYXRhKSkKCiMgRml0IG1vZGVsIHRvIHNpdGUgd2l0aCBob2xlcy4KaG9sZV9mb3JtdWxhIDwtIHkgfiAtMSArIGludGVyY2VwdCArIGYoaWR4LCBtb2RlbCA9IG1hcmdpbl9ob2xlX3NwZGUpICMgTm8gY292YXJpYXRlcy4KaG9sZV9kYXRhIDwtIGxpc3QoeSA9IGhvbGVfZmFrZV9kYXRhLCBpZHggPSAxOmhvbGVfblYsIGludGVyY2VwdCA9IHJlcCgxLCBob2xlX25WKSkKCnN5c3RlbS50aW1lKApyZXN1bHRfaG9sZSA8LSBpbmxhKAogIGZvcm11bGEgPSBob2xlX2Zvcm11bGEsCiAgZGF0YSA9IGhvbGVfZGF0YSwKICBmYW1pbHkgPSAncG9pc3NvbicsCiAgY29udHJvbC5wcmVkaWN0b3IgPSBsaXN0KEEgPSBob2xlX09ic2VydmF0aW9uTWF0cml4KSwKICBFID0gaG9sZV9FX3BvaW50X3Byb2Nlc3MsCiAgdmVyYm9zZSA9IFRSVUUKKQopCgpyZXN1bHRfaG9sZSRzdW1tYXJ5LmZpeGVkCnJlc3VsdF9ob2xlJHN1bW1hcnkuaHlwZXJwYXIKYGBgCgpgYGB7ciBiZWlub2dyaWRob2xlcGxvdCwgY2FjaGUgPSBUUlVFLCBkZXBlbmRzb24gPSAnYmVpbm9ncmlkaG9sZSd9CiMgUGxvdCBzdXJmYWNlLgpwYXIobWFyID0gYygwLjUsIDAsIDIsIDIpKQpwbG90KGltKHQoaW5sYS5tZXNoLnByb2plY3QocHJval9tYXJnaW5faG9sZSwgcmVzdWx0X2hvbGUkc3VtbWFyeS5maXhlZCRtZWFuICsgcmVzdWx0X2hvbGUkc3VtbWFyeS5yYW5kb20kaWR4JG1lYW4pKSwKICAgICAgICB4cmFuZ2UgPSBGcmFtZShiZWkpJHggKyBjKC1NQVJHSU4sIE1BUkdJTiksCiAgICAgICAgeXJhbmdlID0gRnJhbWUoYmVpKSR5ICsgYygtTUFSR0lOLCBNQVJHSU4pLAogICAgICAgIHVuaXRuYW1lID0gYygnbWV0ZXInLCAnbWV0ZXJzJykpLAogICAgICAgIG1haW4gPSAnUG9zdGVyaW9yIE1lYW4gb2YgTG9nLUludGVuc2l0eScpCnBsb3QoV2luZG93KGJlaV9ob2xlKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcG9pbnRzKGJlaV9ob2xlLCBwY2ggPSAnLicsIGNvbCA9ICd3aGl0ZScpCnBhcihtYXIgPSBjKDAuNSwgMCwgMiwgMikpCnBsb3QoaW0odChpbmxhLm1lc2gucHJvamVjdChwcm9qX21hcmdpbl9ob2xlLCBzcXJ0KHJlc3VsdF9ob2xlJHN1bW1hcnkuZml4ZWQkc2ReMiArIHJlc3VsdF9ob2xlJHN1bW1hcnkucmFuZG9tJGlkeCRzZF4yKSkpLAogICAgICAgIHhyYW5nZSA9IEZyYW1lKGJlaSkkeCArIGMoLU1BUkdJTiwgTUFSR0lOKSwKICAgICAgICB5cmFuZ2UgPSBGcmFtZShiZWkpJHkgKyBjKC1NQVJHSU4sIE1BUkdJTiksCiAgICAgICAgdW5pdG5hbWUgPSBjKCdtZXRlcicsICdtZXRlcnMnKSksCiAgICAgICAgbWFpbiA9ICdQb3N0ZXJpb3IgU0Qgb2YgTG9nLUludGVuc2l0eScpCnBsb3QoV2luZG93KGJlaV9ob2xlKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcG9pbnRzKGJlaV9ob2xlLCBwY2ggPSAnLicsIGNvbCA9ICd3aGl0ZScpCmBgYAoKYGBge3IgYmVpbm9ncmlkc2FtcCwgY2FjaGUgPSBUUlVFLCBkZXBlbmRzb24gPSAnZWZmb3J0J30Kc2FtcF9wdHMgPC0gY2JpbmQoYmVpX3NhbXAkeCwgYmVpX3NhbXAkeSkKCiMgQ29udHJ1Y3QgdGhlIFNQREUgQSBtYXRyaXggZm9yIG5vZGVzIGFuZCBwb2ludHMuCnNhbXBfblYgPC0gbWFyZ2luX3NhbXAkbgpzYW1wX25EYXRhIDwtIGRpbShzYW1wX3B0cylbMV0Kc2FtcF9Mb2NhdGlvbk1hdHJpeCA8LSBpbmxhLm1lc2gucHJvamVjdChtYXJnaW5fc2FtcCwgc2FtcF9wdHMpJEEKc2FtcF9JbnRlZ3JhdGlvbk1hdHJpeCA8LSBzcGFyc2VNYXRyaXgoaSA9IDE6c2FtcF9uViwgaiA9IDE6c2FtcF9uViwgeCA9IHJlcCgxLCBzYW1wX25WKSkKc2FtcF9PYnNlcnZhdGlvbk1hdHJpeCA8LSByYmluZChzYW1wX0ludGVncmF0aW9uTWF0cml4LCBzYW1wX0xvY2F0aW9uTWF0cml4KQoKIyBHZXQgdGhlIGludGVncmF0aW9uIHdlaWdodHMuCnNhbXBfSW50ZWdyYXRpb25XZWlnaHRzIDwtIGRpYWcoaW5sYS5tZXNoLmZlbShtYXJnaW5fc2FtcCkkYzApCnNhbXBfRV9wb2ludF9wcm9jZXNzIDwtIGMob2JzX3NhbXAgKiBzYW1wX0ludGVncmF0aW9uV2VpZ2h0cywgcmVwKDAsIHNhbXBfbkRhdGEpKQoKIyBDcmVhdGUgdGhlIHBzdWVkb2RhdGEuCnNhbXBfZmFrZV9kYXRhIDwtIGMocmVwKDAsIHNhbXBfblYpLCByZXAoMSwgc2FtcF9uRGF0YSkpCgojIEZpdCBtb2RlbCB0byBxdWFkcmF0LXNhbXBsZWQgc2l0ZS4Kc2FtcF9mb3JtdWxhIDwtIHkgfiAtMSArIGludGVyY2VwdCArIGYoaWR4LCBtb2RlbCA9IG1hcmdpbl9zYW1wX3NwZGUpICMgTm8gY292YXJpYXRlcy4Kc2FtcF9kYXRhIDwtIGxpc3QoeSA9IHNhbXBfZmFrZV9kYXRhLCBpZHggPSAxOnNhbXBfblYsIGludGVyY2VwdCA9IHJlcCgxLCBzYW1wX25WKSkKCnN5c3RlbS50aW1lKApyZXN1bHRfc2FtcCA8LSBpbmxhKAogIGZvcm11bGEgPSBzYW1wX2Zvcm11bGEsCiAgZGF0YSA9IHNhbXBfZGF0YSwKICBmYW1pbHkgPSAncG9pc3NvbicsCiAgY29udHJvbC5wcmVkaWN0b3IgPSBsaXN0KEEgPSBzYW1wX09ic2VydmF0aW9uTWF0cml4KSwKICBFID0gc2FtcF9FX3BvaW50X3Byb2Nlc3MsCiAgdmVyYm9zZSA9IFRSVUUKKQopCgpyZXN1bHRfc2FtcCRzdW1tYXJ5LmZpeGVkCnJlc3VsdF9zYW1wJHN1bW1hcnkuaHlwZXJwYXIKYGBgCgpgYGB7ciBiZWlub2dyaWRzYW1wcGxvdCwgY2FjaGUgPSBUUlVFLCBkZXBlbmRzb24gPSAnYmVpbm9ncmlkc2FtcCd9CiMgUGxvdCBzdXJmYWNlLgpwYXIobWFyID0gYygwLjUsIDAsIDIsIDIpKQpwbG90KGltKHQoaW5sYS5tZXNoLnByb2plY3QocHJval9tYXJnaW5fc2FtcCwgcmVzdWx0X3NhbXAkc3VtbWFyeS5maXhlZCRtZWFuICsgcmVzdWx0X3NhbXAkc3VtbWFyeS5yYW5kb20kaWR4JG1lYW4pKSwKICAgICAgICB4cmFuZ2UgPSBGcmFtZShiZWkpJHggKyBjKC1NQVJHSU4sIE1BUkdJTiksCiAgICAgICAgeXJhbmdlID0gRnJhbWUoYmVpKSR5ICsgYygtTUFSR0lOLCBNQVJHSU4pLAogICAgICAgIHVuaXRuYW1lID0gYygnbWV0ZXInLCAnbWV0ZXJzJykpLAogICAgICAgIG1haW4gPSAnUG9zdGVyaW9yIE1lYW4gb2YgTG9nLUludGVuc2l0eScpCnBsb3QoV2luZG93KGJlaSksIGJvcmRlciA9ICd3aGl0ZScsIGFkZCA9IFRSVUUpCnBsb3QoV2luZG93KGJlaV9zYW1wKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcG9pbnRzKGJlaV9zYW1wLCBwY2ggPSAnLicsIGNvbCA9ICd3aGl0ZScpCnBhcihtYXIgPSBjKDAuNSwgMCwgMiwgMikpCnBsb3QoaW0odChpbmxhLm1lc2gucHJvamVjdChwcm9qX21hcmdpbl9zYW1wLCBzcXJ0KHJlc3VsdF9zYW1wJHN1bW1hcnkuZml4ZWQkc2ReMiArIHJlc3VsdF9zYW1wJHN1bW1hcnkucmFuZG9tJGlkeCRzZF4yKSkpLAogICAgICAgIHhyYW5nZSA9IEZyYW1lKGJlaSkkeCArIGMoLU1BUkdJTiwgTUFSR0lOKSwKICAgICAgICB5cmFuZ2UgPSBGcmFtZShiZWkpJHkgKyBjKC1NQVJHSU4sIE1BUkdJTiksCiAgICAgICAgdW5pdG5hbWUgPSBjKCdtZXRlcicsICdtZXRlcnMnKSksCiAgICAgICAgbWFpbiA9ICdQb3N0ZXJpb3IgU0Qgb2YgTG9nLUludGVuc2l0eScpCnBsb3QoV2luZG93KGJlaSksIGJvcmRlciA9ICd3aGl0ZScsIGFkZCA9IFRSVUUpCnBsb3QoV2luZG93KGJlaV9zYW1wKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcG9pbnRzKGJlaV9zYW1wLCBwY2ggPSAnLicsIGNvbCA9ICd3aGl0ZScpCmBgYAoKCiMjIEJlaSBEYXRhc2V0IGFuZCBgaW5sYWJydWAKCmBpbmxhYnJ1YCBoYXMgYSB3cmFwcGVyIGZ1bmN0aW9uIHRvIGZpdCBhbiBMR0NQIHdpdGggSU5MQSwgd2l0aCBhIHJlbGF0aXZlbHkKZWFzeS10by11c2UgaW50ZXJmYWNlIGZvciBkZWZpbmluZyBtb2RlbHMgYW5kIHByZWRpY3RpbmcgYXJiaXRyYXJ5IGZ1bmN0aW9ucyBvZgpsYXRlbnQgdmFyaWFibGVzLiBIb3dldmVyLCBpdCBpcyBwb29ybHkgZG9jdW1lbnRlZCwgc2xvdywgYW5kIHRoZSBkb2N1bWVudGF0aW9uCnNlZW1zIHRvIGltcGx5IHRoYXQgaXQgZG9lcyBub3Qgc3VwcG9ydCB2YXJpYWJsZSBzYW1wbGluZyBlZmZvcnQgKGV2ZW4gdGhvdWdoCnRoaXMgYXBwZWFycyB0byB3b3JrKS4KCmBgYHtyIGJlaWZ1bGxsZ2NwLCBjYWNoZSA9IFRSVUUsIGRlcGVuZHNvbiA9ICdlZmZvcnQnfQpiZWlfZnVsbF9zcGRmIDwtIGFzLlNwYXRpYWxQb2ludHMucHBwKGJlaSkKY21wX2Z1bGwgPC0gY29vcmRpbmF0ZXMgfiBteVNtb290aChtYXAgPSBjb29yZGluYXRlcywgbW9kZWwgPSBtYXJnaW5fc3BkZSkgKyBJbnRlcmNlcHQKCnN5c3RlbS50aW1lKApiZWlfZnVsbF9sZ2NwIDwtIGxnY3AoY21wX2Z1bGwsIGJlaV9mdWxsX3NwZGYsIEUgPSBvYnNfZnVsbCwgb3B0aW9ucyA9IGxpc3QodmVyYm9zZSA9IFRSVUUpKQopCgpiZWlfZnVsbF9sZ2NwJHN1bW1hcnkuZml4ZWQKYmVpX2Z1bGxfbGdjcCRzdW1tYXJ5Lmh5cGVycGFyCmBgYAoKYGBge3IgYmVpZnVsbGxnY3BwbG90LCBjYWNoZSA9IFRSVUUsIGRlcGVuZHNvbiA9ICdiZWlmdWxsbGdjcHBsb3QnfQojIFBsb3QgcG9zdGVyaW9yIG1lYW5zIGFuZCBwb3N0ZXJpb3Igc2QuCmxhbWJkYV9mdWxsIDwtIHByZWRpY3QoYmVpX2Z1bGxfbGdjcCwgcGl4ZWxzKG1hcmdpbl9tZXNoKSwgfiBJbnRlcmNlcHQgKyBteVNtb290aCkKcGxvdChsYW1iZGFfZnVsbCwgbWFpbiA9ICdQb3N0ZXJpb3IgTWVhbiBvZiBMb2ctSW50ZW5zaXR5JykKcGxvdChXaW5kb3coYmVpKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcG9pbnRzKGJlaSwgcGNoID0gJy4nLCBjb2wgPSAnd2hpdGUnKQpwbG90KGxhbWJkYV9mdWxsWydzZCddICxtYWluID0gJ1Bvc3RlcmlvciBTRCBvZiBMb2ctSW50ZW5zaXR5JykKcGxvdChXaW5kb3coYmVpKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcG9pbnRzKGJlaSwgcGNoID0gJy4nLCBjb2wgPSAnd2hpdGUnKQpgYGAKCmBgYHtyIGJlaWhvbGVsZ2NwLCBjYWNoZSA9IFRSVUUsIGRlcGVuZHNvbiA9ICdlZmZvcnQnfQpiZWlfaG9sZV9zcGRmIDwtIGFzLlNwYXRpYWxQb2ludHMucHBwKGJlaV9ob2xlKQpjbXBfaG9sZSA8LSBjb29yZGluYXRlcyB+IG15U21vb3RoKG1hcCA9IGNvb3JkaW5hdGVzLCBtb2RlbCA9IG1hcmdpbl9ob2xlX3NwZGUpICsgSW50ZXJjZXB0CgpzeXN0ZW0udGltZSgKYmVpX2hvbGVfbGdjcCA8LSBsZ2NwKGNtcF9ob2xlLCBiZWlfaG9sZV9zcGRmLCBFID0gb2JzX2hvbGUsIG9wdGlvbnMgPSBsaXN0KHZlcmJvc2UgPSBUUlVFKSkKKQoKYmVpX2hvbGVfbGdjcCRzdW1tYXJ5LmZpeGVkCmJlaV9ob2xlX2xnY3Akc3VtbWFyeS5oeXBlcnBhcgpgYGAKCmBgYHtyIGJlaWhvbGVsZ2NwcGxvdCwgY2FjaGUgPSBUUlVFLCBkZXBlbmRzb24gPSAnYmVpaG9sZWxnY3BwbG90J30KIyBQbG90IHBvc3RlcmlvciBtZWFucyBhbmQgcG9zdGVyaW9yIHNkLgpsYW1iZGFfaG9sZSA8LSBwcmVkaWN0KGJlaV9ob2xlX2xnY3AsIHBpeGVscyhtYXJnaW5faG9sZSksIH4gSW50ZXJjZXB0ICsgbXlTbW9vdGgpCnBsb3QobGFtYmRhX2hvbGUsIG1haW4gPSAnUG9zdGVyaW9yIE1lYW4gb2YgTG9nLUludGVuc2l0eScpCnBsb3QoV2luZG93KGJlaV9ob2xlKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcG9pbnRzKGJlaV9ob2xlLCBwY2ggPSAnLicsIGNvbCA9ICd3aGl0ZScpCnBsb3QobGFtYmRhX2hvbGVbJ3NkJ10sIG1haW4gPSAnUG9zdGVyaW9yIFNEIG9mIExvZy1JbnRlbnNpdHknKQpwbG90KFdpbmRvdyhiZWlfaG9sZSksIGJvcmRlciA9ICd3aGl0ZScsIGFkZCA9IFRSVUUpCnBvaW50cyhiZWlfaG9sZSwgcGNoID0gJy4nLCBjb2wgPSAnd2hpdGUnKQpgYGAKCmBgYHtyIGJlaXNhbXBsZ2NwLCBjYWNoZSA9IFRSVUUsIGRlcGVuZHNvbiA9ICdlZmZvcnQnfQpiZWlfc2FtcF9zcGRmIDwtIGFzLlNwYXRpYWxQb2ludHMucHBwKGJlaV9zYW1wKQpjbXBfc2FtcCA8LSBjb29yZGluYXRlcyB+IG15U21vb3RoKG1hcCA9IGNvb3JkaW5hdGVzLCBtb2RlbCA9IG1hcmdpbl9zYW1wX3NwZGUpICsgSW50ZXJjZXB0CgpzeXN0ZW0udGltZSgKYmVpX3NhbXBfbGdjcCA8LSBsZ2NwKGNtcF9zYW1wLCBiZWlfc2FtcF9zcGRmLCBFID0gb2JzX3NhbXAsIG9wdGlvbnMgPSBsaXN0KHZlcmJvc2UgPSBUUlVFKSkKKQoKYmVpX3NhbXBfbGdjcCRzdW1tYXJ5LmZpeGVkCmJlaV9zYW1wX2xnY3Akc3VtbWFyeS5oeXBlcnBhcgpgYGAKCmBgYHtyIGJlaXNhbXBsZ2NwcGxvdCwgY2FjaGUgPSBUUlVFLCBkZXBlbmRzb24gPSAnYmVpc2FtcGxnY3BwbG90J30KIyBQbG90IHBvc3RlcmlvciBtZWFucyBhbmQgcG9zdGVyaW9yIHNkLgpsYW1iZGFfc2FtcCA8LSBwcmVkaWN0KGJlaV9zYW1wX2xnY3AsIHBpeGVscyhtYXJnaW5fc2FtcCksIH4gSW50ZXJjZXB0ICsgbXlTbW9vdGgpCnBsb3QobGFtYmRhX3NhbXAsIG1haW4gPSAnUG9zdGVyaW9yIE1lYW4gb2YgTG9nLUludGVuc2l0eScpCnBsb3QoV2luZG93KGJlaSksIGJvcmRlciA9ICd3aGl0ZScsIGFkZCA9IFRSVUUpCnBsb3QoV2luZG93KGJlaV9zYW1wKSwgYm9yZGVyID0gJ3doaXRlJywgYWRkID0gVFJVRSkKcG9pbnRzKGJlaV9zYW1wLCBwY2ggPSAnLicsIGNvbCA9ICd3aGl0ZScpCnBsb3QobGFtYmRhX3NhbXBbJ3NkJ10sIG1haW4gPSAnUG9zdGVyaW9yIFNEIG9mIExvZy1JbnRlbnNpdHknKQpwbG90KFdpbmRvdyhiZWkpLCBib3JkZXIgPSAnd2hpdGUnLCBhZGQgPSBUUlVFKQpwbG90KFdpbmRvdyhiZWlfc2FtcCksIGJvcmRlciA9ICd3aGl0ZScsIGFkZCA9IFRSVUUpCnBvaW50cyhiZWlfc2FtcCwgcGNoID0gJy4nLCBjb2wgPSAnd2hpdGUnKQpgYGAKCgojIFN1bW1hcnkKCkFsbCB0aHJlZSBtZXRob2RzIGdpdmUgc2ltaWxhciByZXN1bHRzIGZvciB0aGUgZnVsbCBkYXRhc2V0IGFuZCB0aGUgZGF0YXNldAp3aXRoIGhvbGVzLCBldmVuIHdoZW4gdGhlIGdyaWRkaW5nIG1ldGhvZCB1c2VzIGEgY29hcnNlIGdyaWQuIFRoZSBpbnRlcmNlcHQgYW5kCnJhbmRvbSBlZmZlY3RzIGFyZSBzaGlmdGVkIGZyb20gbWV0aG9kIHRvIG1ldGhvZCwgYnV0IHRoZXNlIGFyZSBub3Qgc2VwYXJhdGVseQppbmRlbnRpZmlhYmxlIGFuZCB0aGUgc2hpZnRzIGNhbmNlbCBlYWNoIG90aGVyIG91dC4gVGhlIG1ldGhvZHMgZWFjaCBoYXZlCmRpZmZlcmVudCBhcnRpZmFjdHMgYW5kIGVkZ2UgZWZmZWN0cyBhcHBhcmVudCBpbiB0aGUgcmVzdWx0cyBmcm9tIHRoZSBzYW1wbGVkCmRhdGFzZXQuIFRoZSBwc2V1ZG9kYXRhIGFwcHJvYWNoIGlzIHRoZSBmYXN0ZXN0IGV4Y2VwdCB3aGVuIGEgdmVyeSBjb2Fyc2UgZ3JpZAppcyB1c2VkIGFuZCBhIHNtYWxsIHJlZ2lvbiBpcyBvYnNlcnZlZC4KCgojIFJlZmVyZW5jZXMKCg==