1 Description

This R notebook contains data and analysis regarding physiological measurements obtained from chemostat cultivations of Ralstonia eutropha (a.k.a. Cupriavidus necator).

2 Libraries

suppressPackageStartupMessages({
  library(lattice)
  library(latticeExtra)
  library(latticetools)
  library(tidyverse)
})

3 Chemostat cultivation, biomass and growth rate

3.1 Import and reshape cultivation data

First we load the three cultivation data tables. These contain all four limiting conditions. We are thinning out the OD measurement data by 75% so that file sizes become smaller and more plottable.

# load chemostat measurements
df_chem <- read.csv("../data/input/20180921_chemostat_OD.csv") %>%
  
  # remove outlier data points originating from sampling
  filter(!od_value > 0.6) %>%
  
  # thinning data to reduce file size
  slice_sample(n = 4000)

3.2 OD and growth rate

The next step is to plot the OD720 and growth rate for all cultivations.

plot_OD <- xyplot(od_value ~ batchtime_h | condition, 
  arrange(df_chem, batchtime_h),
  groups = replicate, par.settings = custom.colorblind(),
  layout = c(1,4), as.table = TRUE,
  type = "l", xlim = c(0, 335),
  cols = custom.colorblind()$superpose.polygon$col[1:4],
  between = list(x = 0.5, y = 0.5), lwd = 2,
  ylim = c(-0.06, 0.86), ylab = expression("OD"[720*nm]),
  panel = function(x, y, cols, ...) {
    panel.xyplot(x, y, col = cols[panel.number()], ...)
  }
)

plot_dil <- xyplot(dilution_rate ~ batchtime_h | condition, 
  filter(df_chem, replicate == 1), 
  par.settings = custom.colorblind(), type = "l",
  between = list(x = 0.5, y = 0.5), ylim = c(-0.03, 0.43),
  xlab = "time [h]", ylab = expression("µ [h"^-1*"]"),
  panel = function(x, y, ...){
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.horizonplot(c(x, 335), c(y, max(y)), col.regions = grey(0.7, alpha = 0.5))
    arrow_x <- c(sort(x)[which(diff(sort(y)) != 0)], max(x)-1)
    panel.arrows(x0 = arrow_x, y0 = 0.4, x1 = arrow_x, y1 = 0.32,
      type = "closed", col = grey(0.5), lwd = 2, fill = grey(0.5), 
      length = 0.05, unit = "npc")
  }
)

print(doubleYScale(plot_OD, plot_dil, use.style = FALSE, add.ylab2 = TRUE, under = TRUE))

3.3 Residual substrate concentration

The residual substrate (carbon sources only) was determined using HPLC measurement. In order to merge cultivation and residual substrate measurements, we summarize average OD and growth rate for all cultivations leaving out the first 25 h batch growth phase.

df_summary <- filter(df_chem, batchtime_h > 25) %>%
  
  # summarize average OD
  group_by(condition, dilution_rate, replicate) %>%
  summarize(OD720 = mean(od_value, na.rm = TRUE)) %>%
  
  # then we need medium substrate concentration for FA, FRC, NH4CL, SUC
  mutate(c_substrate = case_when(
    condition == "ammonium" ~ 0.025,
    condition == "formate" ~ 1.0,
    condition == "fructose" ~ 0.5,
    condition == "succinate" ~ 0.5
  ))
`summarise()` has grouped output by 'condition', 'dilution_rate'. You can override using the `.groups` argument.

The residual substrate measurement is imported, and merged with the cultivation summary table. For concnetration of substrate that is really taken up is calculated by subtracting the residual substrate (mg) from feed substrate concentration (g).

df_residual <- read_csv("../data/input/20181116_chemostat_residual_substrate.csv", col_types = cols()) %>%
  rename(condition = limitation) %>%
  mutate(condition = recode(condition, `formic acid` = "formate", `Nlim - fructose` = "ammonium")) %>%
  mutate(c_residual_g_L = mean_concentration_mg.L/1000 %>% replace(., . < 0, 0))
Warning: Missing column names filled in: 'X1' [1]
# join the summary df with residual substrate data
df_summary <- left_join(df_summary, df_residual %>%
    select(condition, dilution_rate, c_residual_g_L) %>%
    filter(!duplicated(c_residual_g_L), condition != "ammonium")) %>%
  mutate(c_residual_g_L = c_residual_g_L %>% replace_na(0))
Joining, by = c("condition", "dilution_rate")
print(head(df_summary))
plot_residual <- xyplot(concentration_mg.L ~ factor(dilution_rate) | condition,
  df_residual,
  par.settings = custom.colorblind(), as.table = TRUE,
  pch = 19, ylim = c(-50, 1450), layout = c(4, 1),
  between = list(x = 0.5, y = 0.5),
  scales = list(alternating = FALSE, x = list(at = c(1,3,5))),
  xlab = expression("µ [h"^-1*"]"), ylab = "concentration [mg/L]",
  panel = function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.barplot(x, y, ewidth = 0.3, lwd = 2, fill = "white", fill_alpha = 1, ...)
    panel.xyplot(x, y, alpha = 0.3, ...)
  }
)

print(plot_residual)

3.4 DCW and PHB content of cells

The next step is to add biomass concentration (DCW) in g/L. For carbon limitations, the DCW was determined in a separate series of 50 mL shake flask cultivations with substrate concentration identical to chemostat experiments (fructose: 0.5 g/L, succinate 0.5 g/L, 4 replicates). The concentration of formate was doubled to obtain sufficient biomass for DCW measurement (2.0 g/L). For ammonium limitation, the cultivation was reproduced in chemostats with an N-limiting concentration of 0.05 g/L NH4Cl and non-limiting carbon concentration of 2.0 g/L fructose.

df_DCW_PHB <- bind_rows(
  read_csv("../data/input/20210810_gDCW_carbon_lim.csv", col_types = cols()),
  read_csv("../data/input/20210810_gDCW_nitrogen_lim.csv", col_types = cols())
) %>%
  filter(condition != "ammonium WT") %>%
  mutate(condition = condition %>% str_replace(" ", "\n") %>%
    str_replace("ammonium", "ammon."))
# plot total gDCW and total gPHB
plot_PHB_Clim <- xyplot(g_DCW_L + g_PHB_L ~ factor(condition),
  filter(df_DCW_PHB, !str_detect(condition, "ammon")),
  par.settings = custom.colorblind(), ylim = c(0, 0.3),
  lwd = 2, xlab = "", ylab = expression("g L"^-1),
  panel=function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.barplot(x, y, beside = TRUE, draw_points = TRUE, ...)
    panel.annotate(x, y, offset = 0.04, digits = 3, beside = TRUE, cex = 0.7, ...)
    panel.key(groups = c("g DCW/L", "g PHB/L"), corner = c(0.95, 0.95))
  }
)

plot_PHB_Nlim <- xyplot(g_DCW_L + g_PHB_L ~ factor(condition, unique(condition)[c(2:6,1)]),
  filter(df_DCW_PHB, str_detect(condition, "ammon")),
  par.settings = custom.colorblind(), ylim = c(0, 0.3),
  lwd = 2, xlab = "", ylab = expression("g L"^-1),
  panel=function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.barplot(x, y, beside = TRUE, draw_points = TRUE, ...)
    panel.annotate(x, y, offset = 0.04, digits = 3, beside = TRUE, cex = 0.7, ...)
    panel.key(groups = c("g DCW/L", "g PHB/L"), corner = c(0.95, 0.95))
  }
)

# plot yield on substrate (excluding PHB)
plot_yield_Clim <- xyplot(g_DCW_g_substrate_noPHB ~ factor(condition),
  filter(df_DCW_PHB, !str_detect(condition, "ammon")),
  par.settings = custom.colorblind(), ylim = c(-0.02, 0.52),
  lwd = 2, xlab = "", ylab = expression("g DCW g S"^-1),
  panel=function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.barplot(x, y, draw_points = TRUE, ...)
    panel.annotate(x, y, offset = 0.1, digits = 3, cex = 0.7, ...)
  }
)

plot_yield_Nlim <- xyplot(g_DCW_g_substrate_noPHB ~ 
    factor(condition, unique(condition)[c(2:6,1)]),
  filter(df_DCW_PHB, str_detect(condition, "ammon")),
  par.settings = custom.colorblind(), ylim = c(-0.1, 2.4),
  lwd = 2, xlab = "", ylab = expression("g DCW g S"^-1),
  panel=function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.barplot(x, y, draw_points = TRUE, ...)
    panel.annotate(x, y, offset = 0.5, digits = 3, cex = 0.7, ...)
  }
)

print(plot_PHB_Clim, position = c(0,0.49,0.42,1), more = TRUE)
print(plot_PHB_Nlim, position = c(0.38,0.463,1,1), more = TRUE)
print(plot_yield_Clim, position = c(0.01,0,0.42,0.51), more = TRUE)
print(plot_yield_Nlim, position = c(0.39,-0.027,1,0.51))
grid::grid.text(c("A", "B", "C", "D"), x = c(0.03, 0.4, 0.03, 0.4),
  y = c(0.97, 0.97, 0.48, 0.48), gp = grid::gpar(cex = 1.2))

3.5 Calculate biomass yield and maintenance

The yield on formate was considerably lower than the maximum biomass yield on formate (0.093 g DCW / g S, Grunwald et al., 2015). The optical density of the cultures was 40% lower than the OD in bioreactors. The explanation is the inhibitory effect of formate. The yield was determined with a starting concentration of 2 g/L which was shown to reduce yield significantly. Grunwald et al. established a linear model to estimate the toxic effect of residual formic (RF) acid concentration: fraction of max yield = (0.169 - 0.049 * RF)/0.169. For RF = 2.0 g/L, the obtained biomass yield is only 0.42 of theoretical maximum. In order to obtain a realistic estimation of yield in chemostats with no or low residual formate, formate yield was corrected based on above relationship for the purpose of modeling.

The substrate uptake rate qS is calculated using the corrected substrate concentration (S_uptake = S_feed - S_residual). The rate qS is then dilution rate times feed concentration divided by experimentally determined biomass in the culture (excluding PHB): qS [g/gDCW] = D [h-1] * c [g/L] / biomass [gDCW/L]. The DCW for formate and ammonium was determined using twice the original concentration, hence the g DCW is divided by two for compatibility.

df_summary <- df_DCW_PHB %>%
  filter(condition != "ammon.\nPHB-") %>%
  mutate(condition = str_replace(condition, "ammon.\nu=.*", "ammonium")) %>%
  group_by(condition, growth_rate) %>%
  
  # summarize replicates to mean biomass and PHB
  summarise(.groups = "drop",
    g_DCW_L = mean(g_DCW_L_noPHB),
    g_PHB_L = mean(g_PHB_L)) %>%
  mutate(
    g_DCW_L = case_when(
      condition == "formate" ~ g_DCW_L/(0.42*2),
      condition == "ammonium" ~ g_DCW_L/2, TRUE ~ g_DCW_L),
    g_PHB_L = case_when(condition %in% c("formate", "ammonium") ~ g_PHB_L/2, TRUE ~ g_PHB_L),
    percent_PHB = g_PHB_L/(g_PHB_L+g_DCW_L)*100
  ) %>%
  
  # fill missing conditions (growth rates), and merge with main df
  rename(dilution_rate = growth_rate) %>%
  replace_na(list(dilution_rate = 0.05)) %>%
  complete(condition, dilution_rate) %>%
  fill(g_DCW_L, g_PHB_L, percent_PHB) %>%
  right_join(df_summary) %>%

  # substrate uptake rate per gDCW
  # qS [g/gDCW] = D [h-1] * c [g/L] / gDCW * L
  mutate(qS_g_gDCW = dilution_rate*(c_substrate-c_residual_g_L)/g_DCW_L) %>%
  
  # substrate uptake rate in C-mol per C-mol biomass (25.35 gDCW/Cmol = 0.03944 Cmol/gDCW)
  # (source: Grunwald et al., Mirco Biotech, 2015)
  mutate(Cmol_g = case_when(
    condition == "formate" ~ 1/46.03 * 1,
    condition == "fructose" ~ 1/180.16 * 6,
    condition == "succinate" ~ 1/118.09 * 4,
    condition == "ammonium" ~ 1/53.49 * 1 # here it is N-mol! NH4Cl has 1 N
  )) %>%
  
  # for NH4Cl, we actually use N-mol per N-mol biomass per h. 
  # N-mol in biomass is around 4x lower than C mol (bionumbers)
  mutate(qS_Cmol_Cmol_DCW = case_when(
    condition == "ammonium" ~ qS_g_gDCW * Cmol_g / (0.03944/4),
    TRUE ~ qS_g_gDCW * Cmol_g / 0.03944
  ))
Joining, by = c("condition", "dilution_rate")

Finally we can create a so-called Herbert-Pirt plot, that is growth rate versus substrate uptake rate. This plot will usually reveal a linear relationship, meaning that substrate uptake and growth are proportional to each other. If there is a kink (change in slope) of the data and one linear model would not be sufficient to describe the data, then the yield has changed, i.e. cells start using a different pathway for energy generation (e.g. respiration versus fermentation). In the default scenario (linear relationship), the yield is the slope of the linear model and the maintenance coeffcient is the intersection with the Y axis.

HP_plot <- function(data, xlimits = c(0, 0.3), ylimits = c(0, 1)) {
  xyplot(qS_g_gDCW ~ dilution_rate | condition, data,
    par.settings = custom.colorblind(), lwd = 1.5,
    xlim = xlimits, ylim = ylimits,
    ylab = expression("q"[S]*" [g h"^-1*" gDCW"^-1*"]"),
    xlab = expression('µ [h'^'-1'*']'),
    panel = function(x, y, ...) {
      panel.grid(h = -1, v = -1, col = grey(0.9))
      panel.xyplot(x, y, cex = 0.9, ...)
      # regression line through linear part of the data (omit µ = 0.25/h)
      if (data$condition[1] == "formate") sel <- x < 0.2 else sel <- x < 0.25
      x = x[sel]; y = y[sel]
      panel.lmline(x, y, ...)
      coef <- lm(y ~ x, data.frame(x, y))$coeff
      # displaying maintenance and yield coefficients
      panel.abline(h = coef[[1]], lty = 2, ...)
      panel.text(0.15, coef[[1]],
        paste("ms =", round(coef[[1]], 3), "g h-1 gDCW-1"),
        col = grey(0.3), pos = 3, cex = 0.7)
      panel.text(0.15, coef[[1]], paste(expression("Yx/S ="),
        round(1/coef[[2]], 3), "gDCW g-1"),
        col = grey(0.3), pos = 1, cex = 0.7)
    }
  )
}

print(HP_plot(data = filter(df_summary, condition == "formate"),
  ylimits = c(-7/4, 7)), position = c(0.017,0.5,0.5,1), more = TRUE)
print(HP_plot(data = filter(df_summary, condition == "fructose"),
  ylimits = c(-1.5/4, 1.5)), position = c(0.5,0.5,1,1), more = TRUE)
print(HP_plot(data = filter(df_summary, condition == "ammonium"),
  ylimits = c(-0.25/4, 0.25)), position = c(-0.017,0,0.5,0.5), more = TRUE)
print(HP_plot(data = filter(df_summary, condition == "succinate"),
  ylimits = c(-1.5/4, 1.5)), position = c(0.5,0,1,0.5))


For metabolic modeling, we shall also calculate the specific productivity of PHB as rate in g PHB per g DCW per hour. We can convert the unit to the convention used in metabolic modeling, mmol per g DCW per hour, using the molecular mass of the 3-hydroxy butyrate monomer, M = 104.1 g/mol, subtracted by one molecule water lost during the condensation into the polymer (M = 104.1 - 18 = 86.1 g/mol = 0.0861 g/mmol).

df_summary <- df_summary %>%
  mutate(
    mmol_PHB_g_DCW = percent_PHB/(100*0.0861),
    P_g_PHB_g_DCW_h = g_PHB_L/g_DCW_L*dilution_rate,
    P_mmol_PHB_g_DCW_h = P_g_PHB_g_DCW_h/0.0861
  )
  
xyplot(P_mmol_PHB_g_DCW_h ~ factor(dilution_rate) | condition, df_summary,
  par.settings = custom.colorblind(), layout = c(4, 1),
  as.table = TRUE, between = list(x = 0.5, y = 0.5),
  ylab = expression("P"[PHB]*" [mmol h"^-1*" gDCW"^-1*"]"),
  xlab = expression('µ [h'^'-1'*']'),
  scales = list(alternating = FALSE, x = list(at = c(1, 3, 5))),
  panel = function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.xyplot(x, y, cex = 0.9, ...)
  }
)


# preview table with mass % and flux to PHB for all conditions
df_summary %>%
  select(condition, dilution_rate, percent_PHB, mmol_PHB_g_DCW, 
    P_g_PHB_g_DCW_h, P_mmol_PHB_g_DCW_h) %>%
  distinct %>% print

3.6 O2 consumption and CO2 emission

Outlet gas composition was analyzed with specific optical sensors for O2 and CO2. The gas uptake rate can then be determined knowing the input concentration, output concentration, flow rate per reactor, and biomass concentration per reactor. First load biomass and gas concentration raw measurements.

df_chemostat_Nlim <- read_csv("../data/input/20190716_chemostat_PHB.csv", col_types = cols()) %>%
  filter(mu <= 0.3) %>% mutate(condition = recode(
    condition, "C-limitation" = "fructose", "N-limitation" = "ammonium")) %>%
  mutate(replicate = if_else(channel > 4, channel-4, channel))

df_gas <- read_csv("../data/input/20190728_chemostat_gas.csv", col_types = cols())

Next we summarize all measurements to one measurement per condition and replicate. Then we use measurements for the replicates to calculate the mean gas uptake and emission rate normalized to biomass concentration.

df_gas <- df_gas %>%
  
  # remove mu higher than 0.3
  filter(mu <= 0.3) %>%
  
  # determine average delta air flow per cond and replicate
  group_by(condition, strain, mu, type, channel, `flow_rate [ml/min]`) %>%
  summarize(flow_mL_min = mean(`flow [ml/min]`)) %>%
  
  # renumber replicates from 1 to n
  group_by(condition, strain, mu, type) %>%
  mutate(replicate = seq_along(channel)) %>%
  arrange(condition, strain, mu, type, replicate) %>%
  
  # obtain biomass concentration from first table
  ungroup %>% left_join(
    group_by(df_chemostat_Nlim, strain, condition, mu) %>%
    summarize(gDCW_mL = mean(`DCW [g/ml]`, na.rm = TRUE)) %>% 
    ungroup
    )
`summarise()` has grouped output by 'condition', 'strain', 'mu', 'type', 'channel'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'strain', 'condition'. You can override using the `.groups` argument.
Joining, by = c("condition", "strain", "mu")

Now that we have the biomass concentration in gDCW/mL, we can determine the CO2 emission and O2. The culture volume is 65 mL and the mass per volume CO2 is 1.842 mg/mL (air at 1 atm and 20 °C). The mass per volume O2 is 1.314 mg/mL (air at 1 atm and 20 °C) The CO2 emission rate can then be calculated as:

qCO2 [mL min^-1 gDCW^-1] = flow_CO2 [mL min^-1] / (biomass gDCW mL^-1 * V_culture mL) qCO2 [g h^-1 gDCW^-1] =

df_gas <- df_gas %>% 
  
  # add volume to mass conversion
  mutate(m_gas_mg_mL = if_else(type == "CO2", 1.842, 1.314)) %>%
  mutate(
    q_mL_min_gDCW = flow_mL_min / (gDCW_mL * 65),
    q_g_h_gDCW = q_mL_min_gDCW * m_gas_mg_mL * 60 / 1000
  )

head(df_gas)
plot_CO2_em <- xyplot(q_g_h_gDCW ~ factor(mu) | condition,
  filter(df_gas, type == "CO2"), groups = strain,
  par.settings = custom.colorblind(), 
  pch = 19, lwd = 2, ylim = c(0, 1.5),
  as.table = TRUE, between = list(x = 0.5, y = 0.5),
  xlab = expression("µ [h"^-1*"]"), 
  ylab = expression("q"[CO2]*" g gDCW"^-1*" h"^-1),
  scales = list(alternating = FALSE),
  panel=function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.errbars(x, y, ewidth = 0, ...)
    panel.key(..., which.panel = 2, corner = c(0.9, 0.9))
  }
)

plot_O2_up <- xyplot(q_g_h_gDCW*-1 ~ factor(mu) | condition,
  filter(df_gas, type == "O2"), groups = strain,
  par.settings = custom.colorblind(), 
  pch = 19, lwd = 2, ylim = c(0, 1.5),
  as.table = TRUE, between = list(x = 0.5, y = 0.5),
  xlab = expression("µ [h"^-1*"]"), 
  ylab = expression("q"[O2]*" g gDCW"^-1*" h"^-1),
  scales = list(alternating = FALSE),
  panel=function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.errbars(x, y, ewidth = 0, ...)
    panel.key(..., which.panel = 2, corner = c(0.9, 0.9))
  }
)

print(plot_CO2_em, split = c(1,1,1,2), more = TRUE)
print(plot_O2_up, split = c(1,2,1,2))

3.7 High throughput imaging of chemostat cultivated cells

The same samples as were used to determine bulk PHB content using nile red fluorescence spectroscopy were also subjected to high throughput imaging. A Nikon microscope was used to acquire up to 5-10 images per replicate and condition. Phase contrast images and Texas Red fluorescence were acquired to identify cell outline/shape and Nile red fluorescnce from PHB staining, respectively. A custom Cell Profiler pipeline was used to automatically identfy cells, filter out non-cell objects by shape/size, and quantify fluorescence.

First we load a processed data frame combining data of fructose and ammonium limitation.

df_imaging <- read_csv("../data/input/20190728_chemostat_imaging.csv", col_types = cols())

Now we can plot the length of the cells and the volume calculated from the major axis length, and from the assumption that cells are rod/cylinder shaped objects. More precisely, cell volume using the volume of a cylinder as model was determined using the height h, (longest/major axis of our cell image), and its radius r (half of the minor axis/diameter of our cells). Then, volume is calculated as V = pi x r^2 x h.

Result: there does not seem to be a strong correlation of growth rate and cell size, neither in length nor volume. This is somewhat unexpected as this relationship was established in E. coli, Synechocystis, P. putida, and many other bacteria. However, the data might be incomplete and this question was not further pursued.

# see graph pars "box.rectangle" for the box, "box.umbrella" for whiskers
custom_boxplot <- custom.colorblind()
custom_boxplot$box.rectangle$lwd = 2
custom_boxplot$box.umbrella$lwd = 2

cell_length <- xyplot(MajorAxisLength_um ~ factor(Metadata_mu) | limitation, 
  df_imaging %>% filter(strain == "WT (PHB+)"),
  par.settings = custom_boxplot,
  scales = list(alternating = FALSE),
  xlab = expression("µ [h"^-1*"]"),
  ylab = expression("major axis length [µm]"),
  ylim = c(-0.5, 6.5),
  as.table = TRUE, between = list(x = 0.5, y = 0.5),
  panel=function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.bwplot(x, y, horizontal = FALSE, pch = "|", 
      notch = FALSE, do.out = FALSE, ...)
    n_cells = tapply(y, x, length)
    panel.text(labels = paste("n > ", min(n_cells, na.rm = TRUE)-1), x = 2, y = 5.5)
  }
)

cell_volume <- xyplot(cell_volume_um3 ~ factor(Metadata_mu) | limitation, 
  df_imaging %>% filter(strain == "WT (PHB+)"),
  par.settings = custom_boxplot,
  scales = list(alternating = FALSE),
  xlab = expression("µ [h"^-1*"]"),
  ylab = expression("cell volume [µm"^3*"]"),
  ylim = c(-3, 33),
  as.table = TRUE, between = list(x = 0.5, y = 0.5),
  panel=function(x, y, ...) {
    panel.grid(h = -1, v = -1, col = grey(0.9))
    panel.bwplot(x, y, horizontal = FALSE, pch = "|", 
      notch = FALSE, do.out = FALSE, ...)
    n_cells = tapply(y, x, length)
    panel.text(labels = paste("n > ", min(n_cells, na.rm = TRUE)-1), x = 2, y = 28)
  }
)

print(cell_length, split = c(1,1,1,2), more = TRUE)
print(cell_volume, split = c(1,2,1,2))

LS0tCnRpdGxlOiAiUGh5c2lvbG9naWNhbCBtZWFzdXJlbWVudHMgZnJvbSAqUi4gZXV0cm9waGEqIGNoZW1vc3RhdHMiCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCLCAlWScpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IAogICAgdGhlbWU6IGNvc21vCiAgICB0b2M6IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCi0tLQoKIyBEZXNjcmlwdGlvbgoKVGhpcyBSIG5vdGVib29rIGNvbnRhaW5zIGRhdGEgYW5kIGFuYWx5c2lzIHJlZ2FyZGluZyBwaHlzaW9sb2dpY2FsIG1lYXN1cmVtZW50cyBvYnRhaW5lZCBmcm9tIGNoZW1vc3RhdCBjdWx0aXZhdGlvbnMgb2YgKlJhbHN0b25pYSBldXRyb3BoYSogKGEuay5hLiAqQ3VwcmlhdmlkdXMgbmVjYXRvciopLgoKIyBMaWJyYXJpZXMKCmBgYHtyLCBtZXNzYWdlcyA9IEZBTFNFfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkobGF0dGljZSkKICBsaWJyYXJ5KGxhdHRpY2VFeHRyYSkKICBsaWJyYXJ5KGxhdHRpY2V0b29scykKICBsaWJyYXJ5KHRpZHl2ZXJzZSkKfSkKYGBgCgojIENoZW1vc3RhdCBjdWx0aXZhdGlvbiwgYmlvbWFzcyBhbmQgZ3Jvd3RoIHJhdGUKCiMjIEltcG9ydCBhbmQgcmVzaGFwZSBjdWx0aXZhdGlvbiBkYXRhCgpGaXJzdCB3ZSBsb2FkIHRoZSB0aHJlZSBjdWx0aXZhdGlvbiBkYXRhIHRhYmxlcy4gVGhlc2UgY29udGFpbiBhbGwgZm91ciBsaW1pdGluZyBjb25kaXRpb25zLiBXZSBhcmUgdGhpbm5pbmcgb3V0IHRoZSBPRCBtZWFzdXJlbWVudCBkYXRhIGJ5IDc1JSBzbyB0aGF0IGZpbGUgc2l6ZXMgYmVjb21lIHNtYWxsZXIgYW5kIG1vcmUgcGxvdHRhYmxlLgoKYGBge3J9CiMgbG9hZCBjaGVtb3N0YXQgbWVhc3VyZW1lbnRzCmRmX2NoZW0gPC0gcmVhZC5jc3YoIi4uL2RhdGEvaW5wdXQvMjAxODA5MjFfY2hlbW9zdGF0X09ELmNzdiIpICU+JQogIAogICMgcmVtb3ZlIG91dGxpZXIgZGF0YSBwb2ludHMgb3JpZ2luYXRpbmcgZnJvbSBzYW1wbGluZwogIGZpbHRlcighb2RfdmFsdWUgPiAwLjYpICU+JQogIAogICMgdGhpbm5pbmcgZGF0YSB0byByZWR1Y2UgZmlsZSBzaXplCiAgc2xpY2Vfc2FtcGxlKG4gPSA0MDAwKQpgYGAKCiMjIE9EIGFuZCBncm93dGggcmF0ZQoKVGhlIG5leHQgc3RlcCBpcyB0byBwbG90IHRoZSBPRDxzdWI+NzIwPC9zdWI+IGFuZCBncm93dGggcmF0ZSBmb3IgYWxsIGN1bHRpdmF0aW9ucy4KCmBgYHtyLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gNS41fQpwbG90X09EIDwtIHh5cGxvdChvZF92YWx1ZSB+IGJhdGNodGltZV9oIHwgY29uZGl0aW9uLCAKICBhcnJhbmdlKGRmX2NoZW0sIGJhdGNodGltZV9oKSwKICBncm91cHMgPSByZXBsaWNhdGUsIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksCiAgbGF5b3V0ID0gYygxLDQpLCBhcy50YWJsZSA9IFRSVUUsCiAgdHlwZSA9ICJsIiwgeGxpbSA9IGMoMCwgMzM1KSwKICBjb2xzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSRzdXBlcnBvc2UucG9seWdvbiRjb2xbMTo0XSwKICBiZXR3ZWVuID0gbGlzdCh4ID0gMC41LCB5ID0gMC41KSwgbHdkID0gMiwKICB5bGltID0gYygtMC4wNiwgMC44NiksIHlsYWIgPSBleHByZXNzaW9uKCJPRCJbNzIwKm5tXSksCiAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCBjb2xzLCAuLi4pIHsKICAgIHBhbmVsLnh5cGxvdCh4LCB5LCBjb2wgPSBjb2xzW3BhbmVsLm51bWJlcigpXSwgLi4uKQogIH0KKQoKcGxvdF9kaWwgPC0geHlwbG90KGRpbHV0aW9uX3JhdGUgfiBiYXRjaHRpbWVfaCB8IGNvbmRpdGlvbiwgCiAgZmlsdGVyKGRmX2NoZW0sIHJlcGxpY2F0ZSA9PSAxKSwgCiAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwgdHlwZSA9ICJsIiwKICBiZXR3ZWVuID0gbGlzdCh4ID0gMC41LCB5ID0gMC41KSwgeWxpbSA9IGMoLTAuMDMsIDAuNDMpLAogIHhsYWIgPSAidGltZSBbaF0iLCB5bGFiID0gZXhwcmVzc2lvbigiwrUgW2giXi0xKiJdIiksCiAgcGFuZWwgPSBmdW5jdGlvbih4LCB5LCAuLi4pewogICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgcGFuZWwuaG9yaXpvbnBsb3QoYyh4LCAzMzUpLCBjKHksIG1heCh5KSksIGNvbC5yZWdpb25zID0gZ3JleSgwLjcsIGFscGhhID0gMC41KSkKICAgIGFycm93X3ggPC0gYyhzb3J0KHgpW3doaWNoKGRpZmYoc29ydCh5KSkgIT0gMCldLCBtYXgoeCktMSkKICAgIHBhbmVsLmFycm93cyh4MCA9IGFycm93X3gsIHkwID0gMC40LCB4MSA9IGFycm93X3gsIHkxID0gMC4zMiwKICAgICAgdHlwZSA9ICJjbG9zZWQiLCBjb2wgPSBncmV5KDAuNSksIGx3ZCA9IDIsIGZpbGwgPSBncmV5KDAuNSksIAogICAgICBsZW5ndGggPSAwLjA1LCB1bml0ID0gIm5wYyIpCiAgfQopCgpwcmludChkb3VibGVZU2NhbGUocGxvdF9PRCwgcGxvdF9kaWwsIHVzZS5zdHlsZSA9IEZBTFNFLCBhZGQueWxhYjIgPSBUUlVFLCB1bmRlciA9IFRSVUUpKQpgYGAKCiMjIFJlc2lkdWFsIHN1YnN0cmF0ZSBjb25jZW50cmF0aW9uCgpUaGUgcmVzaWR1YWwgc3Vic3RyYXRlIChjYXJib24gc291cmNlcyBvbmx5KSB3YXMgZGV0ZXJtaW5lZCB1c2luZyBIUExDIG1lYXN1cmVtZW50LiBJbiBvcmRlciB0byBtZXJnZSBjdWx0aXZhdGlvbiBhbmQgcmVzaWR1YWwgc3Vic3RyYXRlIG1lYXN1cmVtZW50cywgd2Ugc3VtbWFyaXplIGF2ZXJhZ2UgT0QgYW5kIGdyb3d0aCByYXRlIGZvciBhbGwgY3VsdGl2YXRpb25zIGxlYXZpbmcgb3V0IHRoZSBmaXJzdCAyNSBoIGJhdGNoIGdyb3d0aCBwaGFzZS4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmRmX3N1bW1hcnkgPC0gZmlsdGVyKGRmX2NoZW0sIGJhdGNodGltZV9oID4gMjUpICU+JQogIAogICMgc3VtbWFyaXplIGF2ZXJhZ2UgT0QKICBncm91cF9ieShjb25kaXRpb24sIGRpbHV0aW9uX3JhdGUsIHJlcGxpY2F0ZSkgJT4lCiAgc3VtbWFyaXplKE9ENzIwID0gbWVhbihvZF92YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgCiAgIyB0aGVuIHdlIG5lZWQgbWVkaXVtIHN1YnN0cmF0ZSBjb25jZW50cmF0aW9uIGZvciBGQSwgRlJDLCBOSDRDTCwgU1VDCiAgbXV0YXRlKGNfc3Vic3RyYXRlID0gY2FzZV93aGVuKAogICAgY29uZGl0aW9uID09ICJhbW1vbml1bSIgfiAwLjAyNSwKICAgIGNvbmRpdGlvbiA9PSAiZm9ybWF0ZSIgfiAxLjAsCiAgICBjb25kaXRpb24gPT0gImZydWN0b3NlIiB+IDAuNSwKICAgIGNvbmRpdGlvbiA9PSAic3VjY2luYXRlIiB+IDAuNQogICkpCmBgYAoKVGhlIHJlc2lkdWFsIHN1YnN0cmF0ZSBtZWFzdXJlbWVudCBpcyBpbXBvcnRlZCwgYW5kIG1lcmdlZCB3aXRoIHRoZSBjdWx0aXZhdGlvbiBzdW1tYXJ5IHRhYmxlLiBGb3IgY29uY25ldHJhdGlvbiBvZiBzdWJzdHJhdGUgdGhhdCBpcyByZWFsbHkgdGFrZW4gdXAgaXMgY2FsY3VsYXRlZCBieSBzdWJ0cmFjdGluZyB0aGUgcmVzaWR1YWwgc3Vic3RyYXRlIChtZykgZnJvbSBmZWVkIHN1YnN0cmF0ZSBjb25jZW50cmF0aW9uIChnKS4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmRmX3Jlc2lkdWFsIDwtIHJlYWRfY3N2KCIuLi9kYXRhL2lucHV0LzIwMTgxMTE2X2NoZW1vc3RhdF9yZXNpZHVhbF9zdWJzdHJhdGUuY3N2IiwgY29sX3R5cGVzID0gY29scygpKSAlPiUKICByZW5hbWUoY29uZGl0aW9uID0gbGltaXRhdGlvbikgJT4lCiAgbXV0YXRlKGNvbmRpdGlvbiA9IHJlY29kZShjb25kaXRpb24sIGBmb3JtaWMgYWNpZGAgPSAiZm9ybWF0ZSIsIGBObGltIC0gZnJ1Y3Rvc2VgID0gImFtbW9uaXVtIikpICU+JQogIG11dGF0ZShjX3Jlc2lkdWFsX2dfTCA9IG1lYW5fY29uY2VudHJhdGlvbl9tZy5MLzEwMDAgJT4lIHJlcGxhY2UoLiwgLiA8IDAsIDApKQoKIyBqb2luIHRoZSBzdW1tYXJ5IGRmIHdpdGggcmVzaWR1YWwgc3Vic3RyYXRlIGRhdGEKZGZfc3VtbWFyeSA8LSBsZWZ0X2pvaW4oZGZfc3VtbWFyeSwgZGZfcmVzaWR1YWwgJT4lCiAgICBzZWxlY3QoY29uZGl0aW9uLCBkaWx1dGlvbl9yYXRlLCBjX3Jlc2lkdWFsX2dfTCkgJT4lCiAgICBmaWx0ZXIoIWR1cGxpY2F0ZWQoY19yZXNpZHVhbF9nX0wpLCBjb25kaXRpb24gIT0gImFtbW9uaXVtIikpICU+JQogIG11dGF0ZShjX3Jlc2lkdWFsX2dfTCA9IGNfcmVzaWR1YWxfZ19MICU+JSByZXBsYWNlX25hKDApKQoKcHJpbnQoaGVhZChkZl9zdW1tYXJ5KSkKYGBgCgpgYGB7ciwgZmlnLndpZHRoID0gNiwgZmlnLmhlaWdodCA9IDIuNX0KcGxvdF9yZXNpZHVhbCA8LSB4eXBsb3QoY29uY2VudHJhdGlvbl9tZy5MIH4gZmFjdG9yKGRpbHV0aW9uX3JhdGUpIHwgY29uZGl0aW9uLAogIGRmX3Jlc2lkdWFsLAogIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksIGFzLnRhYmxlID0gVFJVRSwKICBwY2ggPSAxOSwgeWxpbSA9IGMoLTUwLCAxNDUwKSwgbGF5b3V0ID0gYyg0LCAxKSwKICBiZXR3ZWVuID0gbGlzdCh4ID0gMC41LCB5ID0gMC41KSwKICBzY2FsZXMgPSBsaXN0KGFsdGVybmF0aW5nID0gRkFMU0UsIHggPSBsaXN0KGF0ID0gYygxLDMsNSkpKSwKICB4bGFiID0gZXhwcmVzc2lvbigiwrUgW2giXi0xKiJdIiksIHlsYWIgPSAiY29uY2VudHJhdGlvbiBbbWcvTF0iLAogIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICBwYW5lbC5iYXJwbG90KHgsIHksIGV3aWR0aCA9IDAuMywgbHdkID0gMiwgZmlsbCA9ICJ3aGl0ZSIsIGZpbGxfYWxwaGEgPSAxLCAuLi4pCiAgICBwYW5lbC54eXBsb3QoeCwgeSwgYWxwaGEgPSAwLjMsIC4uLikKICB9CikKCnByaW50KHBsb3RfcmVzaWR1YWwpCmBgYAoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0KIyBzaWxlbnRseSBleHBvcnQgZmlndXJlCnN2ZygiLi4vZmlndXJlcy9maWd1cmVfY2hlbW9zdGF0X2N1bHQuc3ZnIiwgd2lkdGggPSA1LCBoZWlnaHQgPSA3LjUpCnByaW50KGRvdWJsZVlTY2FsZShwbG90X09ELCBwbG90X2RpbCwgdXNlLnN0eWxlID0gRkFMU0UsIGFkZC55bGFiMiA9IFRSVUUsIHVuZGVyID0gVFJVRSksIAogIHBvc2l0aW9uID0gYygwLDAuMjcsMS4wNywxKSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfcmVzaWR1YWwsIHBvc2l0aW9uID0gYygtMC4wMSwwLDAuOTc1LDAuMzIpKQpkZXYub2ZmKCkKYGBgCgojIyBEQ1cgYW5kIFBIQiBjb250ZW50IG9mIGNlbGxzCgpUaGUgbmV4dCBzdGVwIGlzIHRvIGFkZCBiaW9tYXNzIGNvbmNlbnRyYXRpb24gKERDVykgaW4gZy9MLiBGb3IgY2FyYm9uIGxpbWl0YXRpb25zLCB0aGUgRENXIHdhcyBkZXRlcm1pbmVkIGluIGEgc2VwYXJhdGUgc2VyaWVzIG9mIDUwIG1MIHNoYWtlIGZsYXNrIGN1bHRpdmF0aW9ucyB3aXRoIHN1YnN0cmF0ZSBjb25jZW50cmF0aW9uIGlkZW50aWNhbCB0byBjaGVtb3N0YXQgZXhwZXJpbWVudHMgKGZydWN0b3NlOiAwLjUgZy9MLCBzdWNjaW5hdGUgMC41IGcvTCwgNCByZXBsaWNhdGVzKS4gVGhlIGNvbmNlbnRyYXRpb24gb2YgZm9ybWF0ZSB3YXMgZG91YmxlZCB0byBvYnRhaW4gc3VmZmljaWVudCBiaW9tYXNzIGZvciBEQ1cgbWVhc3VyZW1lbnQgKDIuMCBnL0wpLiBGb3IgYW1tb25pdW0gbGltaXRhdGlvbiwgdGhlIGN1bHRpdmF0aW9uIHdhcyByZXByb2R1Y2VkIGluIGNoZW1vc3RhdHMgd2l0aCBhbiBOLWxpbWl0aW5nIGNvbmNlbnRyYXRpb24gb2YgMC4wNSBnL0wgTkg0Q2wgYW5kIG5vbi1saW1pdGluZyBjYXJib24gY29uY2VudHJhdGlvbiBvZiAyLjAgZy9MIGZydWN0b3NlLgoKYGBge3J9CmRmX0RDV19QSEIgPC0gYmluZF9yb3dzKAogIHJlYWRfY3N2KCIuLi9kYXRhL2lucHV0LzIwMjEwODEwX2dEQ1dfY2FyYm9uX2xpbS5jc3YiLCBjb2xfdHlwZXMgPSBjb2xzKCkpLAogIHJlYWRfY3N2KCIuLi9kYXRhL2lucHV0LzIwMjEwODEwX2dEQ1dfbml0cm9nZW5fbGltLmNzdiIsIGNvbF90eXBlcyA9IGNvbHMoKSkKKSAlPiUKICBmaWx0ZXIoY29uZGl0aW9uICE9ICJhbW1vbml1bSBXVCIpICU+JQogIG11dGF0ZShjb25kaXRpb24gPSBjb25kaXRpb24gJT4lIHN0cl9yZXBsYWNlKCIgIiwgIlxuIikgJT4lCiAgICBzdHJfcmVwbGFjZSgiYW1tb25pdW0iLCAiYW1tb24uIikpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGggPSA3LjUsIGZpZy5oZWlnaHQgPSA2fQojIHBsb3QgdG90YWwgZ0RDVyBhbmQgdG90YWwgZ1BIQgpwbG90X1BIQl9DbGltIDwtIHh5cGxvdChnX0RDV19MICsgZ19QSEJfTCB+IGZhY3Rvcihjb25kaXRpb24pLAogIGZpbHRlcihkZl9EQ1dfUEhCLCAhc3RyX2RldGVjdChjb25kaXRpb24sICJhbW1vbiIpKSwKICBwYXIuc2V0dGluZ3MgPSBjdXN0b20uY29sb3JibGluZCgpLCB5bGltID0gYygwLCAwLjMpLAogIGx3ZCA9IDIsIHhsYWIgPSAiIiwgeWxhYiA9IGV4cHJlc3Npb24oImcgTCJeLTEpLAogIHBhbmVsPWZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgcGFuZWwuYmFycGxvdCh4LCB5LCBiZXNpZGUgPSBUUlVFLCBkcmF3X3BvaW50cyA9IFRSVUUsIC4uLikKICAgIHBhbmVsLmFubm90YXRlKHgsIHksIG9mZnNldCA9IDAuMDQsIGRpZ2l0cyA9IDMsIGJlc2lkZSA9IFRSVUUsIGNleCA9IDAuNywgLi4uKQogICAgcGFuZWwua2V5KGdyb3VwcyA9IGMoImcgRENXL0wiLCAiZyBQSEIvTCIpLCBjb3JuZXIgPSBjKDAuOTUsIDAuOTUpKQogIH0KKQoKcGxvdF9QSEJfTmxpbSA8LSB4eXBsb3QoZ19EQ1dfTCArIGdfUEhCX0wgfiBmYWN0b3IoY29uZGl0aW9uLCB1bmlxdWUoY29uZGl0aW9uKVtjKDI6NiwxKV0pLAogIGZpbHRlcihkZl9EQ1dfUEhCLCBzdHJfZGV0ZWN0KGNvbmRpdGlvbiwgImFtbW9uIikpLAogIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksIHlsaW0gPSBjKDAsIDAuMyksCiAgbHdkID0gMiwgeGxhYiA9ICIiLCB5bGFiID0gZXhwcmVzc2lvbigiZyBMIl4tMSksCiAgcGFuZWw9ZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICBwYW5lbC5iYXJwbG90KHgsIHksIGJlc2lkZSA9IFRSVUUsIGRyYXdfcG9pbnRzID0gVFJVRSwgLi4uKQogICAgcGFuZWwuYW5ub3RhdGUoeCwgeSwgb2Zmc2V0ID0gMC4wNCwgZGlnaXRzID0gMywgYmVzaWRlID0gVFJVRSwgY2V4ID0gMC43LCAuLi4pCiAgICBwYW5lbC5rZXkoZ3JvdXBzID0gYygiZyBEQ1cvTCIsICJnIFBIQi9MIiksIGNvcm5lciA9IGMoMC45NSwgMC45NSkpCiAgfQopCgojIHBsb3QgeWllbGQgb24gc3Vic3RyYXRlIChleGNsdWRpbmcgUEhCKQpwbG90X3lpZWxkX0NsaW0gPC0geHlwbG90KGdfRENXX2dfc3Vic3RyYXRlX25vUEhCIH4gZmFjdG9yKGNvbmRpdGlvbiksCiAgZmlsdGVyKGRmX0RDV19QSEIsICFzdHJfZGV0ZWN0KGNvbmRpdGlvbiwgImFtbW9uIikpLAogIHBhci5zZXR0aW5ncyA9IGN1c3RvbS5jb2xvcmJsaW5kKCksIHlsaW0gPSBjKC0wLjAyLCAwLjUyKSwKICBsd2QgPSAyLCB4bGFiID0gIiIsIHlsYWIgPSBleHByZXNzaW9uKCJnIERDVyBnIFMiXi0xKSwKICBwYW5lbD1mdW5jdGlvbih4LCB5LCAuLi4pIHsKICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgIHBhbmVsLmJhcnBsb3QoeCwgeSwgZHJhd19wb2ludHMgPSBUUlVFLCAuLi4pCiAgICBwYW5lbC5hbm5vdGF0ZSh4LCB5LCBvZmZzZXQgPSAwLjEsIGRpZ2l0cyA9IDMsIGNleCA9IDAuNywgLi4uKQogIH0KKQoKcGxvdF95aWVsZF9ObGltIDwtIHh5cGxvdChnX0RDV19nX3N1YnN0cmF0ZV9ub1BIQiB+IAogICAgZmFjdG9yKGNvbmRpdGlvbiwgdW5pcXVlKGNvbmRpdGlvbilbYygyOjYsMSldKSwKICBmaWx0ZXIoZGZfRENXX1BIQiwgc3RyX2RldGVjdChjb25kaXRpb24sICJhbW1vbiIpKSwKICBwYXIuc2V0dGluZ3MgPSBjdXN0b20uY29sb3JibGluZCgpLCB5bGltID0gYygtMC4xLCAyLjQpLAogIGx3ZCA9IDIsIHhsYWIgPSAiIiwgeWxhYiA9IGV4cHJlc3Npb24oImcgRENXIGcgUyJeLTEpLAogIHBhbmVsPWZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgcGFuZWwuYmFycGxvdCh4LCB5LCBkcmF3X3BvaW50cyA9IFRSVUUsIC4uLikKICAgIHBhbmVsLmFubm90YXRlKHgsIHksIG9mZnNldCA9IDAuNSwgZGlnaXRzID0gMywgY2V4ID0gMC43LCAuLi4pCiAgfQopCgpwcmludChwbG90X1BIQl9DbGltLCBwb3NpdGlvbiA9IGMoMCwwLjQ5LDAuNDIsMSksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X1BIQl9ObGltLCBwb3NpdGlvbiA9IGMoMC4zOCwwLjQ2MywxLDEpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF95aWVsZF9DbGltLCBwb3NpdGlvbiA9IGMoMC4wMSwwLDAuNDIsMC41MSksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X3lpZWxkX05saW0sIHBvc2l0aW9uID0gYygwLjM5LC0wLjAyNywxLDAuNTEpKQpncmlkOjpncmlkLnRleHQoYygiQSIsICJCIiwgIkMiLCAiRCIpLCB4ID0gYygwLjAzLCAwLjQsIDAuMDMsIDAuNCksCiAgeSA9IGMoMC45NywgMC45NywgMC40OCwgMC40OCksIGdwID0gZ3JpZDo6Z3BhcihjZXggPSAxLjIpKQpgYGAKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kc3ZnKCIuLi9maWd1cmVzL2ZpZ3VyZV9EQ1dfUEhCLnN2ZyIsIHdpZHRoID0gNy41LCBoZWlnaHQgPSA2LjApCnByaW50KHBsb3RfUEhCX0NsaW0sIHBvc2l0aW9uID0gYygwLDAuNDksMC40MiwxKSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfUEhCX05saW0sIHBvc2l0aW9uID0gYygwLjM4LDAuNDYzLDEsMSksIG1vcmUgPSBUUlVFKQpwcmludChwbG90X3lpZWxkX0NsaW0sIHBvc2l0aW9uID0gYygwLjAxLDAsMC40MiwwLjUxKSwgbW9yZSA9IFRSVUUpCnByaW50KHBsb3RfeWllbGRfTmxpbSwgcG9zaXRpb24gPSBjKDAuMzksLTAuMDI3LDEsMC41MSkpCmdyaWQ6OmdyaWQudGV4dChjKCJBIiwgIkIiLCAiQyIsICJEIiksIHggPSBjKDAuMDMsIDAuNCwgMC4wMywgMC40KSwKICB5ID0gYygwLjk3LCAwLjk3LCAwLjQ4LCAwLjQ4KSwgZ3AgPSBncmlkOjpncGFyKGNleCA9IDEuMikpCmRldi5vZmYoKQpgYGAKCiMjIENhbGN1bGF0ZSBiaW9tYXNzIHlpZWxkIGFuZCBtYWludGVuYW5jZQoKVGhlIHlpZWxkIG9uIGZvcm1hdGUgd2FzIGNvbnNpZGVyYWJseSBsb3dlciB0aGFuIHRoZSBtYXhpbXVtIGJpb21hc3MgeWllbGQgb24gZm9ybWF0ZSAoMC4wOTMgZyBEQ1cgLyBnIFMsIFtHcnVud2FsZCBldCBhbC4sIDIwMTVdKGRvaS53aWxleS5jb20vMTAuMTExMS8xNzUxLTc5MTUuMTIxNDkpKS4gVGhlIG9wdGljYWwgZGVuc2l0eSBvZiB0aGUgY3VsdHVyZXMgd2FzIDQwJSBsb3dlciB0aGFuIHRoZSBPRCBpbiBiaW9yZWFjdG9ycy4gVGhlIGV4cGxhbmF0aW9uIGlzIHRoZSBpbmhpYml0b3J5IGVmZmVjdCBvZiBmb3JtYXRlLiBUaGUgeWllbGQgd2FzIGRldGVybWluZWQgd2l0aCBhIHN0YXJ0aW5nIGNvbmNlbnRyYXRpb24gb2YgMiBnL0wgd2hpY2ggd2FzIHNob3duIHRvIHJlZHVjZSB5aWVsZCBzaWduaWZpY2FudGx5LiBHcnVud2FsZCBldCBhbC4gZXN0YWJsaXNoZWQgYSBsaW5lYXIgbW9kZWwgdG8gZXN0aW1hdGUgdGhlIHRveGljIGVmZmVjdCBvZiByZXNpZHVhbCBmb3JtaWMgKFJGKSBhY2lkIGNvbmNlbnRyYXRpb246IGZyYWN0aW9uIG9mIG1heCB5aWVsZCA9ICgwLjE2OSAtIDAuMDQ5ICogUkYpLzAuMTY5LiBGb3IgUkYgPSAyLjAgZy9MLCB0aGUgb2J0YWluZWQgYmlvbWFzcyB5aWVsZCBpcyBvbmx5IDAuNDIgb2YgdGhlb3JldGljYWwgbWF4aW11bS4gSW4gb3JkZXIgdG8gb2J0YWluIGEgcmVhbGlzdGljIGVzdGltYXRpb24gb2YgeWllbGQgaW4gY2hlbW9zdGF0cyB3aXRoIG5vIG9yIGxvdyByZXNpZHVhbCBmb3JtYXRlLCBmb3JtYXRlIHlpZWxkIHdhcyBjb3JyZWN0ZWQgYmFzZWQgb24gYWJvdmUgcmVsYXRpb25zaGlwIGZvciB0aGUgcHVycG9zZSBvZiBtb2RlbGluZy4KClRoZSBzdWJzdHJhdGUgdXB0YWtlIHJhdGUgYHFTYCBpcyBjYWxjdWxhdGVkIHVzaW5nIHRoZSBjb3JyZWN0ZWQgc3Vic3RyYXRlIGNvbmNlbnRyYXRpb24gKFNfdXB0YWtlID0gU19mZWVkIC0gU19yZXNpZHVhbCkuIFRoZSByYXRlIGBxU2AgaXMgdGhlbiBkaWx1dGlvbiByYXRlIHRpbWVzIGZlZWQgY29uY2VudHJhdGlvbiBkaXZpZGVkIGJ5IGV4cGVyaW1lbnRhbGx5IGRldGVybWluZWQgYmlvbWFzcyBpbiB0aGUgY3VsdHVyZSAoZXhjbHVkaW5nIFBIQik6IGBxUyBbZy9nRENXXSA9IEQgW2gtMV0gKiBjIFtnL0xdIC8gYmlvbWFzcyBbZ0RDVy9MXWAuIFRoZSBEQ1cgZm9yIGZvcm1hdGUgYW5kIGFtbW9uaXVtIHdhcyBkZXRlcm1pbmVkIHVzaW5nIHR3aWNlIHRoZSBvcmlnaW5hbCBjb25jZW50cmF0aW9uLCBoZW5jZSB0aGUgZyBEQ1cgaXMgZGl2aWRlZCBieSB0d28gZm9yIGNvbXBhdGliaWxpdHkuCgpgYGB7cn0KZGZfc3VtbWFyeSA8LSBkZl9EQ1dfUEhCICU+JQogIGZpbHRlcihjb25kaXRpb24gIT0gImFtbW9uLlxuUEhCLSIpICU+JQogIG11dGF0ZShjb25kaXRpb24gPSBzdHJfcmVwbGFjZShjb25kaXRpb24sICJhbW1vbi5cbnU9LioiLCAiYW1tb25pdW0iKSkgJT4lCiAgZ3JvdXBfYnkoY29uZGl0aW9uLCBncm93dGhfcmF0ZSkgJT4lCiAgCiAgIyBzdW1tYXJpemUgcmVwbGljYXRlcyB0byBtZWFuIGJpb21hc3MgYW5kIFBIQgogIHN1bW1hcmlzZSguZ3JvdXBzID0gImRyb3AiLAogICAgZ19EQ1dfTCA9IG1lYW4oZ19EQ1dfTF9ub1BIQiksCiAgICBnX1BIQl9MID0gbWVhbihnX1BIQl9MKSkgJT4lCiAgbXV0YXRlKAogICAgZ19EQ1dfTCA9IGNhc2Vfd2hlbigKICAgICAgY29uZGl0aW9uID09ICJmb3JtYXRlIiB+IGdfRENXX0wvKDAuNDIqMiksCiAgICAgIGNvbmRpdGlvbiA9PSAiYW1tb25pdW0iIH4gZ19EQ1dfTC8yLCBUUlVFIH4gZ19EQ1dfTCksCiAgICBnX1BIQl9MID0gY2FzZV93aGVuKGNvbmRpdGlvbiAlaW4lIGMoImZvcm1hdGUiLCAiYW1tb25pdW0iKSB+IGdfUEhCX0wvMiwgVFJVRSB+IGdfUEhCX0wpLAogICAgcGVyY2VudF9QSEIgPSBnX1BIQl9MLyhnX1BIQl9MK2dfRENXX0wpKjEwMAogICkgJT4lCiAgCiAgIyBmaWxsIG1pc3NpbmcgY29uZGl0aW9ucyAoZ3Jvd3RoIHJhdGVzKSwgYW5kIG1lcmdlIHdpdGggbWFpbiBkZgogIHJlbmFtZShkaWx1dGlvbl9yYXRlID0gZ3Jvd3RoX3JhdGUpICU+JQogIHJlcGxhY2VfbmEobGlzdChkaWx1dGlvbl9yYXRlID0gMC4wNSkpICU+JQogIGNvbXBsZXRlKGNvbmRpdGlvbiwgZGlsdXRpb25fcmF0ZSkgJT4lCiAgZmlsbChnX0RDV19MLCBnX1BIQl9MLCBwZXJjZW50X1BIQikgJT4lCiAgcmlnaHRfam9pbihkZl9zdW1tYXJ5KSAlPiUKCiAgIyBzdWJzdHJhdGUgdXB0YWtlIHJhdGUgcGVyIGdEQ1cKICAjIHFTIFtnL2dEQ1ddID0gRCBbaC0xXSAqIGMgW2cvTF0gLyBnRENXICogTAogIG11dGF0ZShxU19nX2dEQ1cgPSBkaWx1dGlvbl9yYXRlKihjX3N1YnN0cmF0ZS1jX3Jlc2lkdWFsX2dfTCkvZ19EQ1dfTCkgJT4lCiAgCiAgIyBzdWJzdHJhdGUgdXB0YWtlIHJhdGUgaW4gQy1tb2wgcGVyIEMtbW9sIGJpb21hc3MgKDI1LjM1IGdEQ1cvQ21vbCA9IDAuMDM5NDQgQ21vbC9nRENXKQogICMgKHNvdXJjZTogR3J1bndhbGQgZXQgYWwuLCBNaXJjbyBCaW90ZWNoLCAyMDE1KQogIG11dGF0ZShDbW9sX2cgPSBjYXNlX3doZW4oCiAgICBjb25kaXRpb24gPT0gImZvcm1hdGUiIH4gMS80Ni4wMyAqIDEsCiAgICBjb25kaXRpb24gPT0gImZydWN0b3NlIiB+IDEvMTgwLjE2ICogNiwKICAgIGNvbmRpdGlvbiA9PSAic3VjY2luYXRlIiB+IDEvMTE4LjA5ICogNCwKICAgIGNvbmRpdGlvbiA9PSAiYW1tb25pdW0iIH4gMS81My40OSAqIDEgIyBoZXJlIGl0IGlzIE4tbW9sISBOSDRDbCBoYXMgMSBOCiAgKSkgJT4lCiAgCiAgIyBmb3IgTkg0Q2wsIHdlIGFjdHVhbGx5IHVzZSBOLW1vbCBwZXIgTi1tb2wgYmlvbWFzcyBwZXIgaC4gCiAgIyBOLW1vbCBpbiBiaW9tYXNzIGlzIGFyb3VuZCA0eCBsb3dlciB0aGFuIEMgbW9sIChiaW9udW1iZXJzKQogIG11dGF0ZShxU19DbW9sX0Ntb2xfRENXID0gY2FzZV93aGVuKAogICAgY29uZGl0aW9uID09ICJhbW1vbml1bSIgfiBxU19nX2dEQ1cgKiBDbW9sX2cgLyAoMC4wMzk0NC80KSwKICAgIFRSVUUgfiBxU19nX2dEQ1cgKiBDbW9sX2cgLyAwLjAzOTQ0CiAgKSkKYGBgCgpGaW5hbGx5IHdlIGNhbiBjcmVhdGUgYSBzby1jYWxsZWQgSGVyYmVydC1QaXJ0IHBsb3QsIHRoYXQgaXMgZ3Jvd3RoIHJhdGUgdmVyc3VzIHN1YnN0cmF0ZSB1cHRha2UgcmF0ZS4gVGhpcyBwbG90IHdpbGwgdXN1YWxseSByZXZlYWwgYSBsaW5lYXIgcmVsYXRpb25zaGlwLCBtZWFuaW5nIHRoYXQgc3Vic3RyYXRlIHVwdGFrZSBhbmQgZ3Jvd3RoIGFyZSBwcm9wb3J0aW9uYWwgdG8gZWFjaCBvdGhlci4gSWYgdGhlcmUgaXMgYSBraW5rIChjaGFuZ2UgaW4gc2xvcGUpIG9mIHRoZSBkYXRhIGFuZCBvbmUgbGluZWFyIG1vZGVsIHdvdWxkIG5vdCBiZSBzdWZmaWNpZW50IHRvIGRlc2NyaWJlIHRoZSBkYXRhLCB0aGVuIHRoZSB5aWVsZCBoYXMgY2hhbmdlZCwgaS5lLiBjZWxscyBzdGFydCB1c2luZyBhIGRpZmZlcmVudCBwYXRod2F5IGZvciBlbmVyZ3kgZ2VuZXJhdGlvbiAoZS5nLiByZXNwaXJhdGlvbiB2ZXJzdXMgZmVybWVudGF0aW9uKS4gSW4gdGhlIGRlZmF1bHQgc2NlbmFyaW8gKGxpbmVhciByZWxhdGlvbnNoaXApLCB0aGUgeWllbGQgaXMgdGhlIHNsb3BlIG9mIHRoZSBsaW5lYXIgbW9kZWwgYW5kIHRoZSBtYWludGVuYW5jZSBjb2VmZmNpZW50IGlzIHRoZSBpbnRlcnNlY3Rpb24gd2l0aCB0aGUgWSBheGlzLgoKYGBge3IsIGZpZy53aWR0aCA9IDYuNSwgZmlnLmhlaWdodCA9IDYuNX0KSFBfcGxvdCA8LSBmdW5jdGlvbihkYXRhLCB4bGltaXRzID0gYygwLCAwLjMpLCB5bGltaXRzID0gYygwLCAxKSkgewogIHh5cGxvdChxU19nX2dEQ1cgfiBkaWx1dGlvbl9yYXRlIHwgY29uZGl0aW9uLCBkYXRhLAogICAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwgbHdkID0gMS41LAogICAgeGxpbSA9IHhsaW1pdHMsIHlsaW0gPSB5bGltaXRzLAogICAgeWxhYiA9IGV4cHJlc3Npb24oInEiW1NdKiIgW2cgaCJeLTEqIiBnRENXIl4tMSoiXSIpLAogICAgeGxhYiA9IGV4cHJlc3Npb24oJ8K1IFtoJ14nLTEnKiddJyksCiAgICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICAgIHBhbmVsLnh5cGxvdCh4LCB5LCBjZXggPSAwLjksIC4uLikKICAgICAgIyByZWdyZXNzaW9uIGxpbmUgdGhyb3VnaCBsaW5lYXIgcGFydCBvZiB0aGUgZGF0YSAob21pdCDCtSA9IDAuMjUvaCkKICAgICAgaWYgKGRhdGEkY29uZGl0aW9uWzFdID09ICJmb3JtYXRlIikgc2VsIDwtIHggPCAwLjIgZWxzZSBzZWwgPC0geCA8IDAuMjUKICAgICAgeCA9IHhbc2VsXTsgeSA9IHlbc2VsXQogICAgICBwYW5lbC5sbWxpbmUoeCwgeSwgLi4uKQogICAgICBjb2VmIDwtIGxtKHkgfiB4LCBkYXRhLmZyYW1lKHgsIHkpKSRjb2VmZgogICAgICAjIGRpc3BsYXlpbmcgbWFpbnRlbmFuY2UgYW5kIHlpZWxkIGNvZWZmaWNpZW50cwogICAgICBwYW5lbC5hYmxpbmUoaCA9IGNvZWZbWzFdXSwgbHR5ID0gMiwgLi4uKQogICAgICBwYW5lbC50ZXh0KDAuMTUsIGNvZWZbWzFdXSwKICAgICAgICBwYXN0ZSgibXMgPSIsIHJvdW5kKGNvZWZbWzFdXSwgMyksICJnIGgtMSBnRENXLTEiKSwKICAgICAgICBjb2wgPSBncmV5KDAuMyksIHBvcyA9IDMsIGNleCA9IDAuNykKICAgICAgcGFuZWwudGV4dCgwLjE1LCBjb2VmW1sxXV0sIHBhc3RlKGV4cHJlc3Npb24oIll4L1MgPSIpLAogICAgICAgIHJvdW5kKDEvY29lZltbMl1dLCAzKSwgImdEQ1cgZy0xIiksCiAgICAgICAgY29sID0gZ3JleSgwLjMpLCBwb3MgPSAxLCBjZXggPSAwLjcpCiAgICB9CiAgKQp9CgpwcmludChIUF9wbG90KGRhdGEgPSBmaWx0ZXIoZGZfc3VtbWFyeSwgY29uZGl0aW9uID09ICJmb3JtYXRlIiksCiAgeWxpbWl0cyA9IGMoLTcvNCwgNykpLCBwb3NpdGlvbiA9IGMoMC4wMTcsMC41LDAuNSwxKSwgbW9yZSA9IFRSVUUpCnByaW50KEhQX3Bsb3QoZGF0YSA9IGZpbHRlcihkZl9zdW1tYXJ5LCBjb25kaXRpb24gPT0gImZydWN0b3NlIiksCiAgeWxpbWl0cyA9IGMoLTEuNS80LCAxLjUpKSwgcG9zaXRpb24gPSBjKDAuNSwwLjUsMSwxKSwgbW9yZSA9IFRSVUUpCnByaW50KEhQX3Bsb3QoZGF0YSA9IGZpbHRlcihkZl9zdW1tYXJ5LCBjb25kaXRpb24gPT0gImFtbW9uaXVtIiksCiAgeWxpbWl0cyA9IGMoLTAuMjUvNCwgMC4yNSkpLCBwb3NpdGlvbiA9IGMoLTAuMDE3LDAsMC41LDAuNSksIG1vcmUgPSBUUlVFKQpwcmludChIUF9wbG90KGRhdGEgPSBmaWx0ZXIoZGZfc3VtbWFyeSwgY29uZGl0aW9uID09ICJzdWNjaW5hdGUiKSwKICB5bGltaXRzID0gYygtMS41LzQsIDEuNSkpLCBwb3NpdGlvbiA9IGMoMC41LDAsMSwwLjUpKQpgYGAKCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQojIHNpbGVudGx5IGV4cG9ydCBmaWd1cmUKc3ZnKCIuLi9maWd1cmVzL2ZpZ3VyZV9ncm93dGhfdnNfdXB0YWtlLnN2ZyIsIHdpZHRoID0gNi41LCBoZWlnaHQgPSA2LjUpCnByaW50KEhQX3Bsb3QoZGF0YSA9IGZpbHRlcihkZl9zdW1tYXJ5LCBjb25kaXRpb24gPT0gImZvcm1hdGUiKSwKICB5bGltaXRzID0gYygtNy80LCA3KSksIHBvc2l0aW9uID0gYygwLjAxNywwLjUsMC41LDEpLCBtb3JlID0gVFJVRSkKcHJpbnQoSFBfcGxvdChkYXRhID0gZmlsdGVyKGRmX3N1bW1hcnksIGNvbmRpdGlvbiA9PSAiZnJ1Y3Rvc2UiKSwKICB5bGltaXRzID0gYygtMS41LzQsIDEuNSkpLCBwb3NpdGlvbiA9IGMoMC41LDAuNSwxLDEpLCBtb3JlID0gVFJVRSkKcHJpbnQoSFBfcGxvdChkYXRhID0gZmlsdGVyKGRmX3N1bW1hcnksIGNvbmRpdGlvbiA9PSAiYW1tb25pdW0iKSwKICB5bGltaXRzID0gYygtMC4yNS80LCAwLjI1KSksIHBvc2l0aW9uID0gYygtMC4wMTcsMCwwLjUsMC41KSwgbW9yZSA9IFRSVUUpCnByaW50KEhQX3Bsb3QoZGF0YSA9IGZpbHRlcihkZl9zdW1tYXJ5LCBjb25kaXRpb24gPT0gInN1Y2NpbmF0ZSIpLAogIHlsaW1pdHMgPSBjKC0xLjUvNCwgMS41KSksIHBvc2l0aW9uID0gYygwLjUsMCwxLDAuNSkpCmRldi5vZmYoKQpgYGAKCgotLS0tLS0tLS0tCgpGb3IgbWV0YWJvbGljIG1vZGVsaW5nLCB3ZSBzaGFsbCBhbHNvIGNhbGN1bGF0ZSB0aGUgKipzcGVjaWZpYyBwcm9kdWN0aXZpdHkgb2YgUEhCKiogYXMgcmF0ZSBpbiBnIFBIQiBwZXIgZyBEQ1cgcGVyIGhvdXIuIFdlIGNhbiBjb252ZXJ0IHRoZSB1bml0IHRvIHRoZSBjb252ZW50aW9uIHVzZWQgaW4gbWV0YWJvbGljIG1vZGVsaW5nLCBtbW9sIHBlciBnIERDVyBwZXIgaG91ciwgdXNpbmcgdGhlIG1vbGVjdWxhciBtYXNzIG9mIHRoZSAzLWh5ZHJveHkgYnV0eXJhdGUgbW9ub21lciwgTSA9IDEwNC4xIGcvbW9sLCBzdWJ0cmFjdGVkIGJ5IG9uZSBtb2xlY3VsZSB3YXRlciBsb3N0IGR1cmluZyB0aGUgY29uZGVuc2F0aW9uIGludG8gdGhlIHBvbHltZXIgKE0gPSAxMDQuMSAtIDE4ID0gODYuMSBnL21vbCA9IDAuMDg2MSBnL21tb2wpLgoKYGBge3IsIGZpZy53aWR0aCA9IDYsIGZpZy5oZWlnaHQgPSAyLjV9CmRmX3N1bW1hcnkgPC0gZGZfc3VtbWFyeSAlPiUKICBtdXRhdGUoCiAgICBtbW9sX1BIQl9nX0RDVyA9IHBlcmNlbnRfUEhCLygxMDAqMC4wODYxKSwKICAgIFBfZ19QSEJfZ19EQ1dfaCA9IGdfUEhCX0wvZ19EQ1dfTCpkaWx1dGlvbl9yYXRlLAogICAgUF9tbW9sX1BIQl9nX0RDV19oID0gUF9nX1BIQl9nX0RDV19oLzAuMDg2MQogICkKICAKeHlwbG90KFBfbW1vbF9QSEJfZ19EQ1dfaCB+IGZhY3RvcihkaWx1dGlvbl9yYXRlKSB8IGNvbmRpdGlvbiwgZGZfc3VtbWFyeSwKICBwYXIuc2V0dGluZ3MgPSBjdXN0b20uY29sb3JibGluZCgpLCBsYXlvdXQgPSBjKDQsIDEpLAogIGFzLnRhYmxlID0gVFJVRSwgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksCiAgeWxhYiA9IGV4cHJlc3Npb24oIlAiW1BIQl0qIiBbbW1vbCBoIl4tMSoiIGdEQ1ciXi0xKiJdIiksCiAgeGxhYiA9IGV4cHJlc3Npb24oJ8K1IFtoJ14nLTEnKiddJyksCiAgc2NhbGVzID0gbGlzdChhbHRlcm5hdGluZyA9IEZBTFNFLCB4ID0gbGlzdChhdCA9IGMoMSwgMywgNSkpKSwKICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLikgewogICAgcGFuZWwuZ3JpZChoID0gLTEsIHYgPSAtMSwgY29sID0gZ3JleSgwLjkpKQogICAgcGFuZWwueHlwbG90KHgsIHksIGNleCA9IDAuOSwgLi4uKQogIH0KKQoKIyBwcmV2aWV3IHRhYmxlIHdpdGggbWFzcyAlIGFuZCBmbHV4IHRvIFBIQiBmb3IgYWxsIGNvbmRpdGlvbnMKZGZfc3VtbWFyeSAlPiUKICBzZWxlY3QoY29uZGl0aW9uLCBkaWx1dGlvbl9yYXRlLCBwZXJjZW50X1BIQiwgbW1vbF9QSEJfZ19EQ1csIAogICAgUF9nX1BIQl9nX0RDV19oLCBQX21tb2xfUEhCX2dfRENXX2gpICU+JQogIGRpc3RpbmN0ICU+JSBwcmludApgYGAKCgojIyBPPHN1Yj4yPC9zdWI+IGNvbnN1bXB0aW9uIGFuZCBDTzxzdWI+Mjwvc3ViPiBlbWlzc2lvbgoKT3V0bGV0IGdhcyBjb21wb3NpdGlvbiB3YXMgYW5hbHl6ZWQgd2l0aCBzcGVjaWZpYyBvcHRpY2FsIHNlbnNvcnMgZm9yIE88c3ViPjI8L3N1Yj4gYW5kIENPPHN1Yj4yPC9zdWI+LgpUaGUgZ2FzIHVwdGFrZSByYXRlIGNhbiB0aGVuIGJlIGRldGVybWluZWQga25vd2luZyB0aGUgaW5wdXQgY29uY2VudHJhdGlvbiwgb3V0cHV0IGNvbmNlbnRyYXRpb24sIGZsb3cgcmF0ZSBwZXIgcmVhY3RvciwgYW5kIGJpb21hc3MgY29uY2VudHJhdGlvbiBwZXIgcmVhY3Rvci4gRmlyc3QgbG9hZCBiaW9tYXNzIGFuZCBnYXMgY29uY2VudHJhdGlvbiByYXcgbWVhc3VyZW1lbnRzLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KZGZfY2hlbW9zdGF0X05saW0gPC0gcmVhZF9jc3YoIi4uL2RhdGEvaW5wdXQvMjAxOTA3MTZfY2hlbW9zdGF0X1BIQi5jc3YiLCBjb2xfdHlwZXMgPSBjb2xzKCkpICU+JQogIGZpbHRlcihtdSA8PSAwLjMpICU+JSBtdXRhdGUoY29uZGl0aW9uID0gcmVjb2RlKAogICAgY29uZGl0aW9uLCAiQy1saW1pdGF0aW9uIiA9ICJmcnVjdG9zZSIsICJOLWxpbWl0YXRpb24iID0gImFtbW9uaXVtIikpICU+JQogIG11dGF0ZShyZXBsaWNhdGUgPSBpZl9lbHNlKGNoYW5uZWwgPiA0LCBjaGFubmVsLTQsIGNoYW5uZWwpKQoKZGZfZ2FzIDwtIHJlYWRfY3N2KCIuLi9kYXRhL2lucHV0LzIwMTkwNzI4X2NoZW1vc3RhdF9nYXMuY3N2IiwgY29sX3R5cGVzID0gY29scygpKQpgYGAKCk5leHQgd2Ugc3VtbWFyaXplIGFsbCBtZWFzdXJlbWVudHMgdG8gb25lIG1lYXN1cmVtZW50IHBlciBjb25kaXRpb24gYW5kIHJlcGxpY2F0ZS4gVGhlbiB3ZSB1c2UgbWVhc3VyZW1lbnRzIGZvciB0aGUgcmVwbGljYXRlcyB0byBjYWxjdWxhdGUgdGhlIG1lYW4gZ2FzIHVwdGFrZSBhbmQgZW1pc3Npb24gcmF0ZSBub3JtYWxpemVkIHRvIGJpb21hc3MgY29uY2VudHJhdGlvbi4KCmBgYHtyfQpkZl9nYXMgPC0gZGZfZ2FzICU+JQogIAogICMgcmVtb3ZlIG11IGhpZ2hlciB0aGFuIDAuMwogIGZpbHRlcihtdSA8PSAwLjMpICU+JQogIAogICMgZGV0ZXJtaW5lIGF2ZXJhZ2UgZGVsdGEgYWlyIGZsb3cgcGVyIGNvbmQgYW5kIHJlcGxpY2F0ZQogIGdyb3VwX2J5KGNvbmRpdGlvbiwgc3RyYWluLCBtdSwgdHlwZSwgY2hhbm5lbCwgYGZsb3dfcmF0ZSBbbWwvbWluXWApICU+JQogIHN1bW1hcml6ZShmbG93X21MX21pbiA9IG1lYW4oYGZsb3cgW21sL21pbl1gKSkgJT4lCiAgCiAgIyByZW51bWJlciByZXBsaWNhdGVzIGZyb20gMSB0byBuCiAgZ3JvdXBfYnkoY29uZGl0aW9uLCBzdHJhaW4sIG11LCB0eXBlKSAlPiUKICBtdXRhdGUocmVwbGljYXRlID0gc2VxX2Fsb25nKGNoYW5uZWwpKSAlPiUKICBhcnJhbmdlKGNvbmRpdGlvbiwgc3RyYWluLCBtdSwgdHlwZSwgcmVwbGljYXRlKSAlPiUKICAKICAjIG9idGFpbiBiaW9tYXNzIGNvbmNlbnRyYXRpb24gZnJvbSBmaXJzdCB0YWJsZQogIHVuZ3JvdXAgJT4lIGxlZnRfam9pbigKICAgIGdyb3VwX2J5KGRmX2NoZW1vc3RhdF9ObGltLCBzdHJhaW4sIGNvbmRpdGlvbiwgbXUpICU+JQogICAgc3VtbWFyaXplKGdEQ1dfbUwgPSBtZWFuKGBEQ1cgW2cvbWxdYCwgbmEucm0gPSBUUlVFKSkgJT4lIAogICAgdW5ncm91cAogICAgKQpgYGAKCk5vdyB0aGF0IHdlIGhhdmUgdGhlIGJpb21hc3MgY29uY2VudHJhdGlvbiBpbiBnRENXL21MLCB3ZSBjYW4gZGV0ZXJtaW5lIHRoZSBDTzxzdWI+Mjwvc3ViPiBlbWlzc2lvbiBhbmQgTzxzdWI+Mjwvc3ViPi4gVGhlIGN1bHR1cmUgdm9sdW1lIGlzIDY1IG1MIGFuZCB0aGUgbWFzcyBwZXIgdm9sdW1lIENPPHN1Yj4yPC9zdWI+IGlzIDEuODQyIG1nL21MIChhaXIgYXQgMSBhdG0gYW5kIDIwIMKwQykuIFRoZSBtYXNzIHBlciB2b2x1bWUgTzxzdWI+Mjwvc3ViPiBpcyAxLjMxNCBtZy9tTCAoYWlyIGF0IDEgYXRtIGFuZCAyMCDCsEMpClRoZSBDTzxzdWI+Mjwvc3ViPiBlbWlzc2lvbiByYXRlIGNhbiB0aGVuIGJlIGNhbGN1bGF0ZWQgYXM6CgpxQ08yIFttTCBtaW5eLTEgZ0RDV14tMV0gPSBmbG93X0NPMiBbbUwgbWluXi0xXSAvIChiaW9tYXNzIGdEQ1cgbUxeLTEgKiBWX2N1bHR1cmUgbUwpCnFDTzIgW2cgaF4tMSBnRENXXi0xXSA9IAoKCmBgYHtyfQpkZl9nYXMgPC0gZGZfZ2FzICU+JSAKICAKICAjIGFkZCB2b2x1bWUgdG8gbWFzcyBjb252ZXJzaW9uCiAgbXV0YXRlKG1fZ2FzX21nX21MID0gaWZfZWxzZSh0eXBlID09ICJDTzIiLCAxLjg0MiwgMS4zMTQpKSAlPiUKICBtdXRhdGUoCiAgICBxX21MX21pbl9nRENXID0gZmxvd19tTF9taW4gLyAoZ0RDV19tTCAqIDY1KSwKICAgIHFfZ19oX2dEQ1cgPSBxX21MX21pbl9nRENXICogbV9nYXNfbWdfbUwgKiA2MCAvIDEwMDAKICApCgpoZWFkKGRmX2dhcykKYGBgCgoKCmBgYHtyLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gNS41fQpwbG90X0NPMl9lbSA8LSB4eXBsb3QocV9nX2hfZ0RDVyB+IGZhY3RvcihtdSkgfCBjb25kaXRpb24sCiAgZmlsdGVyKGRmX2dhcywgdHlwZSA9PSAiQ08yIiksIGdyb3VwcyA9IHN0cmFpbiwKICBwYXIuc2V0dGluZ3MgPSBjdXN0b20uY29sb3JibGluZCgpLCAKICBwY2ggPSAxOSwgbHdkID0gMiwgeWxpbSA9IGMoMCwgMS41KSwKICBhcy50YWJsZSA9IFRSVUUsIGJldHdlZW4gPSBsaXN0KHggPSAwLjUsIHkgPSAwLjUpLAogIHhsYWIgPSBleHByZXNzaW9uKCLCtSBbaCJeLTEqIl0iKSwgCiAgeWxhYiA9IGV4cHJlc3Npb24oInEiW0NPMl0qIiBnIGdEQ1ciXi0xKiIgaCJeLTEpLAogIHNjYWxlcyA9IGxpc3QoYWx0ZXJuYXRpbmcgPSBGQUxTRSksCiAgcGFuZWw9ZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICBwYW5lbC5lcnJiYXJzKHgsIHksIGV3aWR0aCA9IDAsIC4uLikKICAgIHBhbmVsLmtleSguLi4sIHdoaWNoLnBhbmVsID0gMiwgY29ybmVyID0gYygwLjksIDAuOSkpCiAgfQopCgpwbG90X08yX3VwIDwtIHh5cGxvdChxX2dfaF9nRENXKi0xIH4gZmFjdG9yKG11KSB8IGNvbmRpdGlvbiwKICBmaWx0ZXIoZGZfZ2FzLCB0eXBlID09ICJPMiIpLCBncm91cHMgPSBzdHJhaW4sCiAgcGFyLnNldHRpbmdzID0gY3VzdG9tLmNvbG9yYmxpbmQoKSwgCiAgcGNoID0gMTksIGx3ZCA9IDIsIHlsaW0gPSBjKDAsIDEuNSksCiAgYXMudGFibGUgPSBUUlVFLCBiZXR3ZWVuID0gbGlzdCh4ID0gMC41LCB5ID0gMC41KSwKICB4bGFiID0gZXhwcmVzc2lvbigiwrUgW2giXi0xKiJdIiksIAogIHlsYWIgPSBleHByZXNzaW9uKCJxIltPMl0qIiBnIGdEQ1ciXi0xKiIgaCJeLTEpLAogIHNjYWxlcyA9IGxpc3QoYWx0ZXJuYXRpbmcgPSBGQUxTRSksCiAgcGFuZWw9ZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICBwYW5lbC5lcnJiYXJzKHgsIHksIGV3aWR0aCA9IDAsIC4uLikKICAgIHBhbmVsLmtleSguLi4sIHdoaWNoLnBhbmVsID0gMiwgY29ybmVyID0gYygwLjksIDAuOSkpCiAgfQopCgpwcmludChwbG90X0NPMl9lbSwgc3BsaXQgPSBjKDEsMSwxLDIpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9PMl91cCwgc3BsaXQgPSBjKDEsMiwxLDIpKQpgYGAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CiMgc2lsZW50bHkgZXhwb3J0IHN2ZyBmaWd1cmVzCnN2ZygiLi4vZmlndXJlcy9maWd1cmVfQ08yX08yLnN2ZyIsIHdpZHRoID0gNSwgaGVpZ2h0ID0gNS41KQpwcmludChwbG90X0NPMl9lbSwgc3BsaXQgPSBjKDEsMSwxLDIpLCBtb3JlID0gVFJVRSkKcHJpbnQocGxvdF9PMl91cCwgc3BsaXQgPSBjKDEsMiwxLDIpKQpkZXYub2ZmKCkKYGBgCgoKIyMgSGlnaCB0aHJvdWdocHV0IGltYWdpbmcgb2YgY2hlbW9zdGF0IGN1bHRpdmF0ZWQgY2VsbHMKClRoZSBzYW1lIHNhbXBsZXMgYXMgd2VyZSB1c2VkIHRvIGRldGVybWluZSBidWxrIFBIQiBjb250ZW50IHVzaW5nIG5pbGUgcmVkIGZsdW9yZXNjZW5jZSBzcGVjdHJvc2NvcHkgd2VyZSBhbHNvIHN1YmplY3RlZCB0byBoaWdoIHRocm91Z2hwdXQgaW1hZ2luZy4KQSBOaWtvbiBtaWNyb3Njb3BlIHdhcyB1c2VkIHRvIGFjcXVpcmUgdXAgdG8gNS0xMCBpbWFnZXMgcGVyIHJlcGxpY2F0ZSBhbmQgY29uZGl0aW9uLiBQaGFzZSBjb250cmFzdCBpbWFnZXMgYW5kIFRleGFzIFJlZCBmbHVvcmVzY2VuY2Ugd2VyZSBhY3F1aXJlZCB0byBpZGVudGlmeSBjZWxsIG91dGxpbmUvc2hhcGUgYW5kIE5pbGUgcmVkIGZsdW9yZXNjbmNlIGZyb20gUEhCIHN0YWluaW5nLCByZXNwZWN0aXZlbHkuIEEgY3VzdG9tIENlbGwgUHJvZmlsZXIgcGlwZWxpbmUgd2FzIHVzZWQgdG8gYXV0b21hdGljYWxseSBpZGVudGZ5IGNlbGxzLCBmaWx0ZXIgb3V0IG5vbi1jZWxsIG9iamVjdHMgYnkgc2hhcGUvc2l6ZSwgYW5kIHF1YW50aWZ5IGZsdW9yZXNjZW5jZS4KCkZpcnN0IHdlIGxvYWQgYSBwcm9jZXNzZWQgZGF0YSBmcmFtZSBjb21iaW5pbmcgZGF0YSBvZiBmcnVjdG9zZSBhbmQgYW1tb25pdW0gbGltaXRhdGlvbi4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmRmX2ltYWdpbmcgPC0gcmVhZF9jc3YoIi4uL2RhdGEvaW5wdXQvMjAxOTA3MjhfY2hlbW9zdGF0X2ltYWdpbmcuY3N2IiwgY29sX3R5cGVzID0gY29scygpKQpgYGAKCk5vdyB3ZSBjYW4gcGxvdCB0aGUgbGVuZ3RoIG9mIHRoZSBjZWxscyBhbmQgdGhlIHZvbHVtZSBjYWxjdWxhdGVkIGZyb20gdGhlIG1ham9yIGF4aXMgbGVuZ3RoLCBhbmQgZnJvbSB0aGUgYXNzdW1wdGlvbiB0aGF0IGNlbGxzIGFyZSByb2QvY3lsaW5kZXIgc2hhcGVkIG9iamVjdHMuIE1vcmUgcHJlY2lzZWx5LCBjZWxsIHZvbHVtZSB1c2luZyB0aGUgdm9sdW1lIG9mIGEgY3lsaW5kZXIgYXMgbW9kZWwgd2FzIGRldGVybWluZWQgdXNpbmcgdGhlIGhlaWdodCBoLCAobG9uZ2VzdC9tYWpvciBheGlzIG9mIG91ciBjZWxsIGltYWdlKSwgYW5kIGl0cyByYWRpdXMgciAoaGFsZiBvZiB0aGUgbWlub3IgYXhpcy9kaWFtZXRlciBvZiBvdXIgY2VsbHMpLiBUaGVuLCB2b2x1bWUgaXMgY2FsY3VsYXRlZCBhcyBWID0gcGkgeCByXjIgeCBoLgoKUmVzdWx0OiB0aGVyZSBkb2VzIG5vdCBzZWVtIHRvIGJlIGEgc3Ryb25nIGNvcnJlbGF0aW9uIG9mIGdyb3d0aCByYXRlIGFuZCBjZWxsIHNpemUsIG5laXRoZXIgaW4gbGVuZ3RoIG5vciB2b2x1bWUuIFRoaXMgaXMgc29tZXdoYXQgdW5leHBlY3RlZCBhcyB0aGlzIHJlbGF0aW9uc2hpcCAgd2FzIGVzdGFibGlzaGVkIGluIEUuIGNvbGksIFN5bmVjaG9jeXN0aXMsIFAuIHB1dGlkYSwgYW5kIG1hbnkgb3RoZXIgYmFjdGVyaWEuIEhvd2V2ZXIsIHRoZSBkYXRhIG1pZ2h0IGJlIGluY29tcGxldGUgYW5kIHRoaXMgcXVlc3Rpb24gd2FzIG5vdCBmdXJ0aGVyIHB1cnN1ZWQuCgpgYGB7ciwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDUuNX0KIyBzZWUgZ3JhcGggcGFycyAiYm94LnJlY3RhbmdsZSIgZm9yIHRoZSBib3gsICJib3gudW1icmVsbGEiIGZvciB3aGlza2VycwpjdXN0b21fYm94cGxvdCA8LSBjdXN0b20uY29sb3JibGluZCgpCmN1c3RvbV9ib3hwbG90JGJveC5yZWN0YW5nbGUkbHdkID0gMgpjdXN0b21fYm94cGxvdCRib3gudW1icmVsbGEkbHdkID0gMgoKY2VsbF9sZW5ndGggPC0geHlwbG90KE1ham9yQXhpc0xlbmd0aF91bSB+IGZhY3RvcihNZXRhZGF0YV9tdSkgfCBsaW1pdGF0aW9uLCAKICBkZl9pbWFnaW5nICU+JSBmaWx0ZXIoc3RyYWluID09ICJXVCAoUEhCKykiKSwKICBwYXIuc2V0dGluZ3MgPSBjdXN0b21fYm94cGxvdCwKICBzY2FsZXMgPSBsaXN0KGFsdGVybmF0aW5nID0gRkFMU0UpLAogIHhsYWIgPSBleHByZXNzaW9uKCLCtSBbaCJeLTEqIl0iKSwKICB5bGFiID0gZXhwcmVzc2lvbigibWFqb3IgYXhpcyBsZW5ndGggW8K1bV0iKSwKICB5bGltID0gYygtMC41LCA2LjUpLAogIGFzLnRhYmxlID0gVFJVRSwgYmV0d2VlbiA9IGxpc3QoeCA9IDAuNSwgeSA9IDAuNSksCiAgcGFuZWw9ZnVuY3Rpb24oeCwgeSwgLi4uKSB7CiAgICBwYW5lbC5ncmlkKGggPSAtMSwgdiA9IC0xLCBjb2wgPSBncmV5KDAuOSkpCiAgICBwYW5lbC5id3Bsb3QoeCwgeSwgaG9yaXpvbnRhbCA9IEZBTFNFLCBwY2ggPSAifCIsIAogICAgICBub3RjaCA9IEZBTFNFLCBkby5vdXQgPSBGQUxTRSwgLi4uKQogICAgbl9jZWxscyA9IHRhcHBseSh5LCB4LCBsZW5ndGgpCiAgICBwYW5lbC50ZXh0KGxhYmVscyA9IHBhc3RlKCJuID4gIiwgbWluKG5fY2VsbHMsIG5hLnJtID0gVFJVRSktMSksIHggPSAyLCB5ID0gNS41KQogIH0KKQoKY2VsbF92b2x1bWUgPC0geHlwbG90KGNlbGxfdm9sdW1lX3VtMyB+IGZhY3RvcihNZXRhZGF0YV9tdSkgfCBsaW1pdGF0aW9uLCAKICBkZl9pbWFnaW5nICU+JSBmaWx0ZXIoc3RyYWluID09ICJXVCAoUEhCKykiKSwKICBwYXIuc2V0dGluZ3MgPSBjdXN0b21fYm94cGxvdCwKICBzY2FsZXMgPSBsaXN0KGFsdGVybmF0aW5nID0gRkFMU0UpLAogIHhsYWIgPSBleHByZXNzaW9uKCLCtSBbaCJeLTEqIl0iKSwKICB5bGFiID0gZXhwcmVzc2lvbigiY2VsbCB2b2x1bWUgW8K1bSJeMyoiXSIpLAogIHlsaW0gPSBjKC0zLCAzMyksCiAgYXMudGFibGUgPSBUUlVFLCBiZXR3ZWVuID0gbGlzdCh4ID0gMC41LCB5ID0gMC41KSwKICBwYW5lbD1mdW5jdGlvbih4LCB5LCAuLi4pIHsKICAgIHBhbmVsLmdyaWQoaCA9IC0xLCB2ID0gLTEsIGNvbCA9IGdyZXkoMC45KSkKICAgIHBhbmVsLmJ3cGxvdCh4LCB5LCBob3Jpem9udGFsID0gRkFMU0UsIHBjaCA9ICJ8IiwgCiAgICAgIG5vdGNoID0gRkFMU0UsIGRvLm91dCA9IEZBTFNFLCAuLi4pCiAgICBuX2NlbGxzID0gdGFwcGx5KHksIHgsIGxlbmd0aCkKICAgIHBhbmVsLnRleHQobGFiZWxzID0gcGFzdGUoIm4gPiAiLCBtaW4obl9jZWxscywgbmEucm0gPSBUUlVFKS0xKSwgeCA9IDIsIHkgPSAyOCkKICB9CikKCnByaW50KGNlbGxfbGVuZ3RoLCBzcGxpdCA9IGMoMSwxLDEsMiksIG1vcmUgPSBUUlVFKQpwcmludChjZWxsX3ZvbHVtZSwgc3BsaXQgPSBjKDEsMiwxLDIpKQpgYGAKCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQojIHNpbGVudGx5IGV4cG9ydCBzdmcgZmlndXJlcwpzdmcoIi4uL2ZpZ3VyZXMvZmlndXJlX2ltYWdpbmcuc3ZnIiwgd2lkdGggPSA1LCBoZWlnaHQgPSA1LjUpCnByaW50KGNlbGxfbGVuZ3RoLCBzcGxpdCA9IGMoMSwxLDEsMiksIG1vcmUgPSBUUlVFKQpwcmludChjZWxsX3ZvbHVtZSwgc3BsaXQgPSBjKDEsMiwxLDIpKQpkZXYub2ZmKCkKYGBgCgo=