This notebook will (attempt) to demonstrate the statistical concepts
behind the “generative” model employed by the proDA package to account
for “dropout” zeros/NAs in proteomics data. Following the mathematics
included in the paper, we will simulate a dataset that exactly matches
the statistical, distributional expectations of the proDA model, and
then examine how the model actually performs with said simulated data.
Along the way, we will gain better understanding of what proDA is
attempting to accomplish, and how it does so …
library(ggplot2)
library(extraDistr)
library(LaplacesDemon)
Attaching package: ‘LaplacesDemon’
The following objects are masked from ‘package:extraDistr’:
dbern, dcat, ddirichlet, dgpd, dgpois, dinvchisq, dinvgamma, dlaplace, dpareto, pbern,
plaplace, ppareto, qbern, qcat, qlaplace, qpareto, rbern, rcat, rdirichlet, rgpd, rinvchisq,
rinvgamma, rlaplace, rpareto
library(reshape2)
library(plyr)
library(ggbeeswarm)
library(proDA)
library(limma)
To fully (?) understand the proDA generative model, we will work
backwards from the various hyperparameters of parent distributions that
define the sharing of means and variances across peptides, and
(eventually) simulate a phony dataset. This, in essence, is exactly what
a generative model represents: an attempt to represent real data via a
simulation, which is governed by definable probability distributions.
Remember: “all models are wrong; some happen to be useful” (George
Box).
The best way to deal with a generative model is to “work backwards”.
Let us begin:
Starting with Appendix equation (4), the variance (squared s.d.),
\(\sigma_i^2\), of each peptide \(i\) is sampled from an Inverse Scaled
Chi-Squared distribution with hyperparameters \(\tt{df}_0\) and \(\tau_0^2\); the rinvchisq R
function expects a parameterization of \(\nu\) (positive shape parameter, same as
\(\tt{df}_0\)) and \(\tau\) (positive scale, or in this case the
inverse of variance, precision). Let’s see what that looks like
for a (really and completely arbitrary) pair of hyperparameters:
set.seed(123) # the well-known "Jackson Five" constant.
npep <- 5000 # number of peptides in our simulation
df_0 <- 20
tau_0 <- 4
var_i <- rinvchisq(npep, df_0, tau_0)
names(var_i) <- paste("fragment", 1:npep, sep="-")
hist(var_i, 100, freq=F)

OK, moving further backwards to Appendix equation (3), this is the
so-called “non-standardized” version of the Student’s t-distribution,
with three parameters: the “normality” parameter \(\tt{df}_{\tt{loc}}\), the central overall
“grand mean” peptide log2 abundance \(\mu_0\), and the variance \(\sigma_0^2\) i.e. range expected
of the individual peptide abundances for each peptide in each group,
\(\mu_{i,j}\) sampled from this
distribution. In the proDA paper, they assume that \(\tt{df}_{\tt{loc}}\) is pre-specified, and
is not estimated from the data; in the code the default is 3. Let’s see
what that looks like for some (again, somewhat arbitrarily selected) set
of hyperparameters:
groups <- c("Ctrl", "TrtA", "TrtB")
ngrp <- length(groups)
df_loc <- 3
mu_0 <- 15
sd_0 <- 1
mu_ij <- matrix(rst(n = npep*ngrp, mu = mu_0, sigma = sd_0^2, nu = df_loc),
ncol=ngrp)
colnames(mu_ij) <- groups
rownames(mu_ij) <- names(var_i)
names(dimnames(mu_ij)) <- c("peptide", "group")
hist(mu_ij, 100, freq=F, xlim=c(5, 25))

OK, so now we have parent or “background” distributions for the means
and variance of each peptide: with different means for the same peptide
in different experimental groups, but enforcing (“assuming”)
shared/constant variance across all experimental groups for each
peptide, i.e. “homoscedasticity” – that’s a really big assumption, and
one that real data very often violates. We can return to that problem
later.
So now we’re ready to simulate the (latent, unobserved) log2
abundance values, \(z_{i,j}\), for
every sample replicate in each group (Appendix equation (1), second
line). These are the values we would have observed in the MS if there
were absolutely no dropout, in which case every log2 abundance would be
measured directly, with some biological (and technical) variation \(\sigma_i^2\).
nrep <- 3 # number of replicates per group
z_ij <- matrix(data = NA, nrow=npep, ncol=0)
for (idx in 1:ncol(mu_ij)) {
group <- groups[idx]
reps <- matrix(rnorm(nrep*npep, mean=mu_ij[,idx], sd=sqrt(var_i)),
byrow = F,
ncol=nrep)
colnames(reps) <- paste(group, rep(1:nrep), sep="-")
z_ij <- cbind(z_ij, reps)
}
rownames(z_ij) <- rownames(mu_ij)
names(dimnames(z_ij)) <- c("peptide", "sampleID")
print(head(z_ij), digits=4)
sampleID
peptide Ctrl-1 Ctrl-2 Ctrl-3 TrtA-1 TrtA-2 TrtA-3 TrtB-1 TrtB-2 TrtB-3
fragment-1 11.25 10.90 11.71 17.63 13.65 15.24 18.70 19.56 25.45
fragment-2 15.87 14.57 12.74 14.09 13.43 12.87 14.80 15.21 13.99
fragment-3 16.32 18.00 14.57 13.84 21.55 17.57 13.54 13.07 11.32
fragment-4 19.14 14.26 17.61 13.69 11.30 14.26 13.50 15.92 12.76
fragment-5 14.95 11.66 13.66 12.74 16.65 16.51 13.15 15.34 13.47
fragment-6 14.71 16.44 16.37 14.57 13.61 19.26 16.16 14.12 11.93
Just to confirm (sanity check) that we did this right, let’s make a
plot of the intended group means, the \(\mu_{i,j}\), with (+/- 2 s.d., from \(\sigma_i^2\)) for each group, and overlay
the \(z_{i,j}\) values, for the first
handful of fragments:
zs <- melt(z_ij[1:6,], value.name = "abundance") # first six peptides
zs$group <- gsub("(.*?)\\-\\d", "\\1", zs$sampleID)
mus <- melt(mu_ij[1:6,], value.name = "abundance")
mus <- merge(mus, melt(var_i[1:6], value.name = "var"), by.x="peptide", by.y=0)
ggplot(zs, aes(x=group, y=abundance, color=sampleID)) + geom_point() +
scale_color_brewer(palette="Set1") +
geom_crossbar(data=mus, mapping = aes(y=abundance,
ymin=abundance-2*sqrt(var),
ymax=abundance+2*sqrt(var)),
color=I("black")) +
facet_wrap(~peptide) + theme_bw()

OK, so with that confirmed, let’s remember that this is a rather
idealized dataset where data from every sample comes already
“pre-normalized” to all the others. Let’s see what I mean:
ggplot(melt(z_ij, value.name = "abundance"),
aes(x=sampleID, y=abundance, color=sampleID)) + geom_violin() +
scale_color_brewer(palette="Set1") + theme_bw()

In real, “raw” data, the distribution of abundances in each sample
would vary a little bit, reflecting differences in sample concentration
relative to each other (especially in a multiplexed experiment).
Between-sample normalization techniques (quantile normalize, median
normalization, etc.) are used to “align” the distributions, but we don’t
need to do such a step here. However, as discussed in the proDA paper,
real “raw” data does generally need to be normalized, and that
normalization step can (and does) introduce distortions when the “lower
end” of the abundance distribution has a higher dropout rate, leading to
more dropout NAs (or zeroes) – especially when the entire distribution
for a given sample is shifted lower, due to an overall lower relative
sample abundance (more dropouts/zeroes than in other samples).
OK, now to simulate the dropouts in each sample. The proDA model uses
a sigmoid curve (using the complement of the Normal CDF function for
convenience, equation 2), with parameters \(\rho_j\) (inflection point) and \(\zeta_j\) (scale) to represent the dropoff
probability, at any given abundance value. Let’s look at a few example
dropout curves for a couple different parameter choices:
ipnorm <- function(...) {
1 - pnorm(...)
}
ggplot() +
stat_function(fun = ipnorm,
color = "blue",
args = list(mean = 1, sd = 1)) +
stat_function(fun = ipnorm,
color="red",
args = list(mean=10, sd=5)) +
geom_vline(xintercept=1, color="blue", linetype=2) +
geom_vline(xintercept=10, color="red", linetype=2) +
geom_hline(yintercept=0.5, color="grey", linetype=2) +
xlim(-10, 30) + xlab("abundance") + ylab("dropout probability") + theme_bw()

From these two example dropout curves, we can see that the inflection
point (\(\rho_j\), specified as the
mean to pnorm Normal CDF function) is where the probability
is exactly 0.5 (see dotted lines); and the scale (\(\zeta_j\), specified as the s.d. to
pnorm defines the slope of the curve, how “sharp” the
demarcation is between high and low dropout probability over a given
range of abundances).
OK, with some intuition in hand now, we can start to reason about how
the dropout curves might differ for each sample. In the proDA model, the
sample-specific inflection points and scales don’t have any common,
shared parent distribution, but are estimated individually for each
sample, based on an optimization of the likelihood function for each
sample; since there are so many peptide fragments to inform that
parameter estimation, that is quite computationally efficient. However,
we’ve been entirely probabilistic so far in this simulation, with every
value of interest derived as a random draw from some parent
distribution, so let’s stick with that theme. Given that we specified
the “grand mean” \(\mu_0\) of all
peptide abundances at 15, with a “range” \(\sigma_0^2\) of 1, let’s say we set the
“average” inflection point (where the dropout probability will be 0.5),
\(\rho_0\), over all samples to be
\(\mu_0 - 4\sigma_0\), 4 times the SD;
and then let each sample’s \(\rho_j\)
vary from this by \(\sigma_{\rho_0}^2\). Similarly, let’s let
the average scale, \(\zeta_0\) be a
hyperparameter, and draw each sample’s \(\zeta_j\) from the InvChiSquare
distribution as we did earlier for the analagous peptide variances:
rho_0 <- mu_0 - 4 * sd_0
sigma_rho_0 <- 1.2
rho_j <- rnorm(ncol(z_ij), rho_0, sigma_rho_0)
names(rho_j) <- colnames(z_ij)
zeta_0 <- 10
zeta_tau_0 <- 3
zeta_j <- rinvchisq(ncol(z_ij), zeta_0, zeta_tau_0)
names(zeta_j) <- colnames(z_ij)
ggplot() +
lapply(1:length(zeta_j), function (i) {
stat_function(fun = ipnorm,
mapping = aes(color=colnames(z_ij)[i]),
args = list(mean=rho_j[i], sd=zeta_j[i]))
}) +
scale_color_brewer(name="sampleID", palette="Set1") +
xlim(-10, 30) + xlab("abundance") + ylab("dropout probability") + theme_bw()

OK, great; we’ve got the underlying dropout curves, one for each
sample. Now we can simulate which of the \(z_{i,j}\) values will be observed (not a
dropout) and which will be “NA” (is a dropout), by flipping a coin with
the probability of dropout defined for each \(z_{i,j}\) abundance value given by the
dropout curve for sample \(j\). This
the “dropout matrix”, \(d_{i,j}\).
d_ij <- z_ij
d_ij[,] <- 0
for (j in 1:ncol(d_ij)) {
d_ij[,j] <- rbern(n=nrow(d_ij), prob=ipnorm(z_ij[,j], rho_j[j], zeta_j[j]))
}
qplot(rho_j, 100 * colSums(d_ij)/nrow(d_ij), color=zeta_j, size=I(5)) +
geom_label(aes(label=names(zeta_j))) +
scale_y_log10() +
ylab("% of dropouts in sample") + theme_bw()

From the above, we see that the range of dropout % in each sample
correlates with the \(\rho_j\)
inflection points – the higher (right-shifted) the inflection point, the
more abundance values are lost due to dropout; and also, for a given
\(\rho_j\) value, the higher the \(\zeta_j\) value, the shallower the dropoff
slope, and so even more abundances dropout.
Now we have everything we need from Appendix equation (1) to produce
a final, simulated dataset of experimentally observed abundances, \(y_{i,j}\).
# start by just copying z_ij into y_ij:
y_ij <- z_ij
# now dropout the values according to d_ij:
for (j in 1:ncol(y_ij)) {
y_ij[d_ij[,j] == 1, j] <- NA
}
print(head(y_ij), digits=4)
sampleID
peptide Ctrl-1 Ctrl-2 Ctrl-3 TrtA-1 TrtA-2 TrtA-3 TrtB-1 TrtB-2 TrtB-3
fragment-1 11.25 10.90 NA 17.63 NA 15.24 18.70 19.56 25.45
fragment-2 15.87 14.57 12.74 14.09 13.43 12.87 14.80 15.21 NA
fragment-3 16.32 NA NA 13.84 21.55 17.57 13.54 13.07 11.32
fragment-4 19.14 14.26 17.61 13.69 NA 14.26 13.50 15.92 NA
fragment-5 NA 11.66 13.66 12.74 16.65 16.51 13.15 15.34 NA
fragment-6 14.71 16.44 16.37 14.57 13.61 19.26 16.16 NA NA
In the first six fragments above, we can already see a handful of
NA’s that represent simulated dropouts.
OK, now we can test proDA using our simulated data, for which we know
the true values for each single parameter of interest, and we can see
how well proDA is able to recover what we’ve setup:
fit <- proDA(y_ij, design = factor(rep(groups, each=nrep)), verbose=T)
Fitting the hyper-parameters for the probabilistic dropout model.
Starting iter: 1
So, perhaps not too unsurprisingly, when we use proDA to generate a
fit with data that exactly matches the modeling expectations, it seems
to do a pretty good job! Let’s look at the various hyperparameters
first:
hps <- fit$hyper_parameters
knitr::kable(
data.frame(
name=c("grand mean",
"grand range",
"normality parameter",
"variance scale",
"variance shape"),
values=c(mu_0, sd_0^2, df_loc, tau_0^2, df_0),
estimate=c(hps$location_prior_mean,
hps$location_prior_scale,
hps$location_prior_df, # given as a fixed constant, not estimated
hps$variance_prior_scale,
hps$variance_prior_df)), format="pipe")
| grand mean |
15 |
15.004408 |
| grand range |
1 |
3.581907 |
| normality parameter |
3 |
3.000000 |
| variance scale |
16 |
3.949251 |
| variance shape |
20 |
18.304869 |
While the parameters (and random seed) of this notebook could change,
at least with the current (?) values, we see that nearly all of the
hyperparameters match very well, with the exception of the “grand
range”, or \(\sigma_0^2\), which proDA
has over-estimated as larger than the true distributed range of
group-wise mean values; I expect this is an undue influence of the
dropout values themselves, which will tend to “inflate” the expected
variance of the group means, at the bottom/left end, even though the
top/right end is accurately and explictly represented in the data (this
amounts to a suggestion for the proDA authors to consider estimating the
variance from a left-truncated distribution of group means, to avoid
this artifact).
Next, let’s checkout the dropout curve parameters:
ggplot(rbind(data.frame(variable="dropout position",
value=rho_j,
estimate=hps$dropout_curve_position,
sampleID = names(rho_j)),
data.frame(variable="dropout scale",
value=zeta_j,
estimate=-hps$dropout_curve_scale,
sampleID = names(zeta_j))),
aes(x=value, y=estimate)) +
geom_abline(slope=1, intercept=0) +
stat_smooth(method="lm", formula = y ~ x) +
geom_point(aes(color=sampleID)) +
facet_wrap(~variable, scales="free") + theme_bw()

Interesting! While both the location (\(\rho\)) and scale (\(\zeta\)) parameters are very strongly
correlated, the location is very slightly under-estimated (though not
statistically signicant in this case), while the scale is distinctly and
systematically over-estimated in all samples. Off the top of my
head, I don’t have any rationale for this behavior, so I’m going to
leave this as a question for the authors (!?!)
OK, so most importantly, let’s look at how accurate proDA’s
estimation of the group means ended up, in the face of all these
dropouts, and (mostly) correct hyperparameter estimations:
coef <- fit$coefficients
names(dimnames(coef)) <- c("peptide", "group")
dtall <- melt(d_ij, value.name = "drop")
dtall$group <- gsub("(.*?)\\-\\d+", "\\1", dtall$sampleID)
dropdf <- ddply(dtall, .(peptide, group), function(dx) {
data.frame(dropnum = sum(dx$drop))
})
ses <- matrix(unlist(lapply(lapply(fit$coefficient_variance_matrices, diag), sqrt)),
ncol=3, byrow=T)
colnames(ses) <- colnames(fit$coefficient_variance_matrices[[1]])
ses <- as.data.frame(ses)
ses$peptide <- rownames(fit$coefficients)
ses$lowqt <- qt(0.025, fit$feature_parameters$df)
ses$highqt <- qt(0.975, fit$feature_parameters$df)
cis <- melt(ses,
id=c("peptide", "highqt", "lowqt"),
variable.name = "group",
value.name = "ses")
cis$low <- cis$lowqt * cis$ses
cis$high <- cis$highqt * cis$ses
df <- merge(dropdf, merge(melt(coef, value.name = "estimate"), melt(mu_ij, value.name = "value")))
df <- merge(df, cis)
df$estlow <- df$estimate + df$low
df$esthigh <- df$estimate + df$high
df$estrange <- df$esthigh - df$estlow
ggplot(df, aes(x=value, y=estimate, shape=group, color=factor(dropnum))) +
geom_point() + geom_abline(slope=1, intercept=0) +
scale_color_brewer(name="# dropouts", palette="Set2") +
facet_wrap(~dropnum) + theme_bw()

Again, interesting! When the number of dropouts in a group is 0, 1,
or even 2, then proDA does a pretty remarkable job recovering the true
value of the group mean. But when all of the replicates in a group are
dropouts, we’re left with coefficient estimates that are all ~12. The
95% confidence interval ranges look like this:
ggplot(df, aes(x=estrange, fill=factor(dropnum))) +
stat_bin(bins=50) +
scale_color_brewer(name="# dropouts", palette="Set2") +
facet_wrap(~dropnum, scales="free_y", ncol=1) + theme_bw() +
xlab("width of 95% confidence interval")

So, as expected: tighter interval ranges for groups having all
observations; wider ranges with less observations, and essentially fixed
(wide) interval when no observations are made.
Lastly, let’s see how well proDA was able to estimate variances for
each peptide:
vardf <- data.frame(value = var_i,
estimate = fit$feature_parameters$s2,
missing = ncol(y_ij) - fit$feature_parameters$n_obs)
ggplot(vardf, aes(x=value, y=estimate)) + stat_binhex() +
geom_abline(slope=1, intercept=0) + theme_bw() +
xlab("true variance") + ylab("proDA estimated variance")

The majority of variances are correctly estimated, but a handful of
peptides having the largest variances remain under-estimated,
potentially inflating any downstream contrast statistics.
effects of normalization with dropouts
We observed earlier that the simulated data is already perfectly
normalized, at least prior to having random low-abundance values removed
due to dropout; and each sample has slightly different dropout curves,
so now the distributions may not be so perfectly comparable:
ggplot(melt(y_ij, value.name = "abundance"),
aes(x=sampleID, y=abundance, color=sampleID)) + geom_violin() +
scale_color_brewer(palette="Set1") + theme_bw()

What happens if we quantile normalize the observations, and then
compare to their original values (removing/ignoring any difference less
than 0.2 units, for graphical clarity)?
normy <- normalizeBetweenArrays(y_ij, method="quantile")
medy <- median_normalization(y_ij)
deltas <- colMeans(medy - y_ij, na.rm=T)
deltas <- data.frame(delta=deltas, sampleID = names(deltas))
ggplot(subset(melt(normy - y_ij, value.name = "delta"),
abs(delta) >= 0.2),
aes(x=sampleID, y=delta, color=sampleID)) + geom_quasirandom() +
scale_color_brewer(palette="Set1") + theme_bw()

It appears, at least for this simulated dataset that doesn’t have
gross differences in mean abundances or dropout rates, that quantile
normalization has a large effect on only a small number of fragments. If
we refit proDA to this (re)normalized data, we’d expect to
see some differences:
normfit <- proDA(normy, design = factor(rep(groups, each=nrep)), verbose=T)
Fitting the hyper-parameters for the probabilistic dropout model.
Starting iter: 1
The inferred parameters are:
location_prior_mean: 14.8
location_prior_scale: 3.99
dropout_curve_position: 10.2, 10.7, 11.2, 9.6, 10.7, 11.5, 9.11, 10, 12.5
dropout_curve_scale: -4.64, -5.09, -5.11, -4.26, -4.89, -5.46, -3.62, -4.5, -6.11
variance_prior_scale: 4.24
variance_prior_df: 12.8
Error: 0.59
Starting iter: 2
Warning in solve.default(coef_hessian) :
system is computationally singular: reciprocal condition number = 1.73106e-16
The inferred parameters are:
location_prior_mean: 14.8
location_prior_scale: 3.94
dropout_curve_position: 9.9, 10.4, 10.9, 9.33, 10.4, 11.3, 8.88, 9.75, 12.3
dropout_curve_scale: -4.94, -5.48, -5.52, -4.52, -5.26, -5.96, -3.79, -4.79, -6.75
variance_prior_scale: 4.01
variance_prior_df: 16.3
Error: 0.13
Starting iter: 3
Warning in solve.default(coef_hessian) :
system is computationally singular: reciprocal condition number = 1.50063e-16
The inferred parameters are:
location_prior_mean: 14.9
location_prior_scale: 3.79
dropout_curve_position: 9.76, 10.2, 10.8, 9.2, 10.3, 11.1, 8.79, 9.62, 12.2
dropout_curve_scale: -5.13, -5.74, -5.83, -4.67, -5.52, -6.32, -3.88, -4.97, -7.28
variance_prior_scale: 3.99
variance_prior_df: 16.8
Error: 0.047
Starting iter: 4
Warning in solve.default(coef_hessian) :
system is computationally singular: reciprocal condition number = 1.3672e-16
The inferred parameters are:
location_prior_mean: 14.9
location_prior_scale: 3.68
dropout_curve_position: 9.7, 10.2, 10.7, 9.17, 10.2, 11, 8.76, 9.57, 12.1
dropout_curve_scale: -5.24, -5.87, -6, -4.74, -5.66, -6.54, -3.93, -5.07, -7.62
variance_prior_scale: 3.98
variance_prior_df: 17.3
Error: 0.017
Starting iter: 5
Warning in solve.default(coef_hessian) :
system is computationally singular: reciprocal condition number = 1.29826e-16
The inferred parameters are:
location_prior_mean: 15
location_prior_scale: 3.61
dropout_curve_position: 9.68, 10.2, 10.7, 9.15, 10.2, 11, 8.76, 9.54, 12
dropout_curve_scale: -5.3, -5.95, -6.11, -4.78, -5.75, -6.67, -3.96, -5.13, -7.85
variance_prior_scale: 3.97
variance_prior_df: 17.7
Error: 0.0066
Starting iter: 6
Warning in solve.default(coef_hessian) :
system is computationally singular: reciprocal condition number = 1.26042e-16
The inferred parameters are:
location_prior_mean: 15
location_prior_scale: 3.56
dropout_curve_position: 9.67, 10.1, 10.7, 9.14, 10.2, 11, 8.76, 9.53, 12
dropout_curve_scale: -5.34, -6.01, -6.19, -4.81, -5.8, -6.76, -3.97, -5.16, -8.01
variance_prior_scale: 3.96
variance_prior_df: 17.9
Error: 0.0032
Starting iter: 7
Warning in solve.default(coef_hessian) :
system is computationally singular: reciprocal condition number = 1.23447e-16
The inferred parameters are:
location_prior_mean: 15
location_prior_scale: 3.52
dropout_curve_position: 9.66, 10.1, 10.6, 9.14, 10.2, 11, 8.76, 9.52, 12
dropout_curve_scale: -5.37, -6.04, -6.24, -4.82, -5.83, -6.82, -3.98, -5.19, -8.09
variance_prior_scale: 3.96
variance_prior_df: 18.2
Error: 0.0016
Starting iter: 8
Warning in solve.default(coef_hessian) :
system is computationally singular: reciprocal condition number = 1.21986e-16
Converged!
The inferred parameters are:
location_prior_mean: 15
location_prior_scale: 3.5
dropout_curve_position: 9.66, 10.1, 10.6, 9.14, 10.1, 11, 8.77, 9.52, 12
dropout_curve_scale: -5.37, -6.07, -6.26, -4.83, -5.86, -6.85, -3.98, -5.19, -8.17
variance_prior_scale: 3.96
variance_prior_df: 18.3
Error: 0.00052
normcoef <- normfit$coefficients
names(dimnames(normcoef)) <- c("peptide", "group")
coefdiff <- normcoef - coef
names(dimnames(coefdiff)) <- c("peptide", "group")
ggplot(subset(melt(coefdiff, value.name = "diff"),
abs(diff) >= 0.2),
aes(x=group, y=diff)) + geom_quasirandom() +
theme_bw() + ylab("difference in coefficient estimates")

ggplot(merge(melt(coefdiff, value.name="diff"),
melt(coef, value.name="estimate")),
aes(x=estimate, y=diff)) + geom_point() + facet_wrap(~group) + theme_bw() +
ylab("difference in coefficient estimates") + xlab("original estimate")

Perhaps as expected, the few coefficients that are grossly affected
by the normalization procedure are confined to the (mostly upper)
extremes on the abundance scale.
But let’s also look at what happens to the dropout curve
parameters:
normhps <- normfit$hyper_parameters
ggplot(rbind(data.frame(variable="dropout position",
value=rho_j,
estimate=normhps$dropout_curve_position,
sampleID = names(rho_j)),
data.frame(variable="dropout scale",
value=zeta_j,
estimate=-normhps$dropout_curve_scale,
sampleID = names(zeta_j))),
aes(x=value, y=estimate)) +
geom_abline(slope=1, intercept=0) +
stat_smooth(method="lm", formula = y ~ x) +
geom_point(aes(color=sampleID)) +
facet_wrap(~variable, scales="free") + theme_bw()

The consequence of renormalizing forces the dropout midpoint
locations to be uniformly over/under estimated for curves having true
lower/higher locations (and the scaling factor remains as before,
uniformly over-estimated). These small distortions in dropout curves
could have knock-on effects for estimation of coefficients from groups
having multiple dropout samples:
normses <- matrix(unlist(lapply(lapply(normfit$coefficient_variance_matrices, diag), sqrt)),
ncol=3, byrow=T)
colnames(normses) <- colnames(normfit$coefficient_variance_matrices[[1]])
normses <- as.data.frame(normses)
normses$peptide <- rownames(normfit$coefficients)
normses$lowqt <- qt(0.025, normfit$feature_parameters$df)
normses$highqt <- qt(0.975, normfit$feature_parameters$df)
normcis <- melt(normses,
id=c("peptide", "highqt", "lowqt"),
variable.name = "group",
value.name = "ses")
normcis$low <- normcis$lowqt * normcis$ses
normcis$high <- normcis$highqt * normcis$ses
normdf <- merge(dropdf, merge(melt(normcoef, value.name = "estimate"), melt(mu_ij, value.name = "value")))
normdf <- merge(normdf, normcis)
normdf$estlow <- normdf$estimate + normdf$low
normdf$esthigh <- normdf$estimate + normdf$high
normdf$estrange <- normdf$esthigh - normdf$estlow
ggplot(normdf, aes(x=value, y=estimate, shape=group, color=factor(dropnum))) +
geom_point() + geom_abline(slope=1, intercept=0) +
scale_color_brewer(name="# dropouts", palette="Set2") +
facet_wrap(~dropnum) + theme_bw()

Sample QC using distances
We know that differences in dropout abundances can lead to spurious
PCA projections during sample-level QC; proDA provides a
probabilistic distance measurement that could avoid such. Let’s
explore:
maty <- y_ij
maty[is.na(maty)] <- log2(1)
maty <- normalizeBetweenArrays(maty, method="quantile")
pca <- prcomp(maty, scale=T)
ggplot(as.data.frame(pca$rotation), aes(x=PC1, y=PC2, color=colnames(maty))) +
geom_point() + theme_bw()

dm <- dist_approx(fit)
heatmap(as.matrix(dm$mean))

While each sample has fewer/more dropouts, the heatmap pairwise
sample distances appear uniform within/between each experimental group,
as expected.
LS0tCnRpdGxlOiAicHJvREEgbW9kZWwgc2ltdWxhdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBub3RlYm9vayB3aWxsIChhdHRlbXB0KSB0byBkZW1vbnN0cmF0ZSB0aGUgc3RhdGlzdGljYWwgY29uY2VwdHMgYmVoaW5kIHRoZSAiZ2VuZXJhdGl2ZSIgbW9kZWwgZW1wbG95ZWQgYnkgdGhlIHByb0RBIHBhY2thZ2UgdG8gYWNjb3VudCBmb3IgImRyb3BvdXQiIHplcm9zL05BcyBpbiBwcm90ZW9taWNzIGRhdGEuIEZvbGxvd2luZyB0aGUgbWF0aGVtYXRpY3MgaW5jbHVkZWQgaW4gdGhlIHBhcGVyLCB3ZSB3aWxsIHNpbXVsYXRlIGEgZGF0YXNldCB0aGF0IGV4YWN0bHkgbWF0Y2hlcyB0aGUgc3RhdGlzdGljYWwsIGRpc3RyaWJ1dGlvbmFsIGV4cGVjdGF0aW9ucyBvZiB0aGUgcHJvREEgbW9kZWwsIGFuZCB0aGVuIGV4YW1pbmUgaG93IHRoZSBtb2RlbCBhY3R1YWxseSBwZXJmb3JtcyB3aXRoIHNhaWQgc2ltdWxhdGVkIGRhdGEuIEFsb25nIHRoZSB3YXksIHdlIHdpbGwgZ2FpbiBiZXR0ZXIgdW5kZXJzdGFuZGluZyBvZiB3aGF0IHByb0RBIGlzIGF0dGVtcHRpbmcgdG8gYWNjb21wbGlzaCwgYW5kIGhvdyBpdCBkb2VzIHNvIC4uLgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShleHRyYURpc3RyKQpsaWJyYXJ5KExhcGxhY2VzRGVtb24pCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkocGx5cikKbGlicmFyeShnZ2JlZXN3YXJtKQpsaWJyYXJ5KHByb0RBKQpsaWJyYXJ5KGxpbW1hKQpgYGAKClRvIGZ1bGx5ICg/KSB1bmRlcnN0YW5kIHRoZSBwcm9EQSBnZW5lcmF0aXZlIG1vZGVsLCB3ZSB3aWxsIHdvcmsgYmFja3dhcmRzIGZyb20KdGhlIHZhcmlvdXMgaHlwZXJwYXJhbWV0ZXJzIG9mIHBhcmVudCBkaXN0cmlidXRpb25zIHRoYXQgZGVmaW5lIHRoZSBzaGFyaW5nIG9mCm1lYW5zIGFuZCB2YXJpYW5jZXMgYWNyb3NzIHBlcHRpZGVzLCBhbmQgKGV2ZW50dWFsbHkpIHNpbXVsYXRlIGEgcGhvbnkgZGF0YXNldC4gVGhpcywgaW4gZXNzZW5jZSwgaXMgZXhhY3RseSB3aGF0IGEgZ2VuZXJhdGl2ZSBtb2RlbCByZXByZXNlbnRzOiBhbiBhdHRlbXB0IHRvICByZXByZXNlbnQgcmVhbCBkYXRhIHZpYSBhIHNpbXVsYXRpb24sIHdoaWNoIGlzIGdvdmVybmVkIGJ5IGRlZmluYWJsZSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb25zLiBSZW1lbWJlcjogImFsbCBtb2RlbHMgYXJlIHdyb25nOyBzb21lIGhhcHBlbiB0byBiZSB1c2VmdWwiIChHZW9yZ2UgQm94KS4KClRoZSBiZXN0IHdheSB0byBkZWFsIHdpdGggYSBnZW5lcmF0aXZlIG1vZGVsIGlzIHRvICJ3b3JrIGJhY2t3YXJkcyIuIExldCB1cyBiZWdpbjoKClN0YXJ0aW5nIHdpdGggQXBwZW5kaXggZXF1YXRpb24gKDQpLCB0aGUgdmFyaWFuY2UgKHNxdWFyZWQgcy5kLiksICRcc2lnbWFfaV4yJCwKb2YgZWFjaCBwZXB0aWRlICRpJCBpcyBzYW1wbGVkIGZyb20gYW4gSW52ZXJzZSBTY2FsZWQgQ2hpLVNxdWFyZWQgZGlzdHJpYnV0aW9uCndpdGggaHlwZXJwYXJhbWV0ZXJzICRcdHR7ZGZ9XzAkIGFuZCAkXHRhdV8wXjIkOyB0aGUgYHJpbnZjaGlzcWAgUiBmdW5jdGlvbiBleHBlY3RzIGEgcGFyYW1ldGVyaXphdGlvbiBvZiAkXG51JCAocG9zaXRpdmUgc2hhcGUgcGFyYW1ldGVyLCBzYW1lIGFzICRcdHR7ZGZ9XzAkKSBhbmQgJFx0YXUkIChwb3NpdGl2ZSBzY2FsZSwgb3IgaW4gdGhpcyBjYXNlIHRoZSBpbnZlcnNlIG9mIHZhcmlhbmNlLCBfcHJlY2lzaW9uXykuIExldCdzIHNlZSB3aGF0IHRoYXQgbG9va3MgbGlrZSBmb3IgYSAocmVhbGx5IGFuZCBjb21wbGV0ZWx5IGFyYml0cmFyeSkgcGFpciBvZiBoeXBlcnBhcmFtZXRlcnM6CgpgYGB7cn0Kc2V0LnNlZWQoMTIzKSAjIHRoZSB3ZWxsLWtub3duICJKYWNrc29uIEZpdmUiIGNvbnN0YW50LgpucGVwIDwtIDUwMDAgIyBudW1iZXIgb2YgcGVwdGlkZXMgaW4gb3VyIHNpbXVsYXRpb24KZGZfMCA8LSAyMAp0YXVfMCA8LSA0CnZhcl9pIDwtIHJpbnZjaGlzcShucGVwLCBkZl8wLCB0YXVfMCkKbmFtZXModmFyX2kpIDwtIHBhc3RlKCJmcmFnbWVudCIsIDE6bnBlcCwgc2VwPSItIikKaGlzdCh2YXJfaSwgMTAwLCBmcmVxPUYpCmBgYApPSywgbW92aW5nIGZ1cnRoZXIgYmFja3dhcmRzIHRvIEFwcGVuZGl4IGVxdWF0aW9uICgzKSwgdGhpcyBpcyB0aGUgc28tY2FsbGVkICJub24tc3RhbmRhcmRpemVkIiB2ZXJzaW9uIG9mIHRoZSBTdHVkZW50J3MgdC1kaXN0cmlidXRpb24sIHdpdGggdGhyZWUgcGFyYW1ldGVyczogdGhlICJub3JtYWxpdHkiIHBhcmFtZXRlciAkXHR0e2RmfV97XHR0e2xvY319JCwgdGhlIGNlbnRyYWwgb3ZlcmFsbCAiZ3JhbmQgbWVhbiIgcGVwdGlkZSBsb2cyIGFidW5kYW5jZSAkXG11XzAkLCBhbmQgdGhlIHZhcmlhbmNlICRcc2lnbWFfMF4yJCBpLmUuIF9yYW5nZV8gZXhwZWN0ZWQgb2YgdGhlIGluZGl2aWR1YWwgcGVwdGlkZSBhYnVuZGFuY2VzIGZvciBlYWNoIHBlcHRpZGUgaW4gZWFjaCBncm91cCwgJFxtdV97aSxqfSQgc2FtcGxlZCBmcm9tIHRoaXMgZGlzdHJpYnV0aW9uLiBJbiB0aGUgcHJvREEgcGFwZXIsIHRoZXkgYXNzdW1lIHRoYXQgJFx0dHtkZn1fe1x0dHtsb2N9fSQgaXMgcHJlLXNwZWNpZmllZCwgYW5kIGlzIG5vdCBlc3RpbWF0ZWQgZnJvbSB0aGUgZGF0YTsgaW4gdGhlIGNvZGUgdGhlIGRlZmF1bHQgaXMgMy4gTGV0J3Mgc2VlIHdoYXQgdGhhdCBsb29rcyBsaWtlIGZvciBzb21lIChhZ2Fpbiwgc29tZXdoYXQgYXJiaXRyYXJpbHkgc2VsZWN0ZWQpIHNldCBvZiBoeXBlcnBhcmFtZXRlcnM6CmBgYHtyfQpncm91cHMgPC0gYygiQ3RybCIsICJUcnRBIiwgIlRydEIiKQpuZ3JwIDwtIGxlbmd0aChncm91cHMpCmRmX2xvYyA8LSAzCm11XzAgPC0gMTUKc2RfMCA8LSAxCm11X2lqIDwtIG1hdHJpeChyc3QobiA9IG5wZXAqbmdycCwgbXUgPSBtdV8wLCBzaWdtYSA9IHNkXzBeMiwgbnUgPSBkZl9sb2MpLAogICAgICAgICAgICAgICAgbmNvbD1uZ3JwKQpjb2xuYW1lcyhtdV9paikgPC0gZ3JvdXBzCnJvd25hbWVzKG11X2lqKSA8LSBuYW1lcyh2YXJfaSkKbmFtZXMoZGltbmFtZXMobXVfaWopKSA8LSBjKCJwZXB0aWRlIiwgImdyb3VwIikKCgpoaXN0KG11X2lqLCAxMDAsIGZyZXE9RiwgeGxpbT1jKDUsIDI1KSkKYGBgCk9LLCBzbyBub3cgd2UgaGF2ZSBwYXJlbnQgb3IgImJhY2tncm91bmQiIGRpc3RyaWJ1dGlvbnMgZm9yIHRoZSBtZWFucyBhbmQgdmFyaWFuY2Ugb2YgZWFjaCBwZXB0aWRlOiB3aXRoIGRpZmZlcmVudCBtZWFucyBmb3IgdGhlIHNhbWUgcGVwdGlkZSBpbiBkaWZmZXJlbnQgZXhwZXJpbWVudGFsIGdyb3VwcywgYnV0IGVuZm9yY2luZyAoImFzc3VtaW5nIikgc2hhcmVkL2NvbnN0YW50IHZhcmlhbmNlIGFjcm9zcyBhbGwgZXhwZXJpbWVudGFsIGdyb3VwcyBmb3IgZWFjaCBwZXB0aWRlLCBpLmUuICJob21vc2NlZGFzdGljaXR5IiAtLSB0aGF0J3MgYSByZWFsbHkgYmlnIGFzc3VtcHRpb24sIGFuZCBvbmUgdGhhdCByZWFsIGRhdGEgdmVyeSBvZnRlbiB2aW9sYXRlcy4gV2UgY2FuIHJldHVybiB0byB0aGF0IHByb2JsZW0gbGF0ZXIuCgpTbyBub3cgd2UncmUgcmVhZHkgdG8gc2ltdWxhdGUgdGhlIChsYXRlbnQsIHVub2JzZXJ2ZWQpIGxvZzIgYWJ1bmRhbmNlIHZhbHVlcywgJHpfe2ksan0kLCBmb3IgZXZlcnkgc2FtcGxlIHJlcGxpY2F0ZSBpbiBlYWNoIGdyb3VwIChBcHBlbmRpeCBlcXVhdGlvbiAoMSksIHNlY29uZCBsaW5lKS4gVGhlc2UgYXJlIHRoZSB2YWx1ZXMgd2Ugd291bGQgaGF2ZSBvYnNlcnZlZCBpbiB0aGUgTVMgaWYgdGhlcmUgd2VyZSBhYnNvbHV0ZWx5IG5vIGRyb3BvdXQsIGluIHdoaWNoIGNhc2UgZXZlcnkgbG9nMiBhYnVuZGFuY2Ugd291bGQgYmUgbWVhc3VyZWQgZGlyZWN0bHksIHdpdGggc29tZSBiaW9sb2dpY2FsIChhbmQgdGVjaG5pY2FsKSB2YXJpYXRpb24gJFxzaWdtYV9pXjIkLgoKYGBge3J9Cm5yZXAgPC0gMyAjIG51bWJlciBvZiByZXBsaWNhdGVzIHBlciBncm91cAp6X2lqIDwtIG1hdHJpeChkYXRhID0gTkEsIG5yb3c9bnBlcCwgbmNvbD0wKQpmb3IgKGlkeCBpbiAxOm5jb2wobXVfaWopKSB7CiAgZ3JvdXAgPC0gZ3JvdXBzW2lkeF0KICByZXBzIDwtIG1hdHJpeChybm9ybShucmVwKm5wZXAsIG1lYW49bXVfaWpbLGlkeF0sIHNkPXNxcnQodmFyX2kpKSwKICAgICAgICAgICAgICAgICBieXJvdyA9IEYsCiAgICAgICAgICAgICAgICAgbmNvbD1ucmVwKQogIGNvbG5hbWVzKHJlcHMpIDwtIHBhc3RlKGdyb3VwLCByZXAoMTpucmVwKSwgc2VwPSItIikKICB6X2lqIDwtIGNiaW5kKHpfaWosIHJlcHMpCn0Kcm93bmFtZXMoel9paikgPC0gcm93bmFtZXMobXVfaWopCm5hbWVzKGRpbW5hbWVzKHpfaWopKSA8LSBjKCJwZXB0aWRlIiwgInNhbXBsZUlEIikKcHJpbnQoaGVhZCh6X2lqKSwgZGlnaXRzPTQpCmBgYAoKSnVzdCB0byBjb25maXJtIChzYW5pdHkgY2hlY2spIHRoYXQgd2UgZGlkIHRoaXMgcmlnaHQsIGxldCdzIG1ha2UgYSBwbG90IG9mIHRoZSBpbnRlbmRlZCBncm91cCBtZWFucywgdGhlICRcbXVfe2ksan0kLCB3aXRoICgrLy0gMiBzLmQuLCBmcm9tICRcc2lnbWFfaV4yJCkgZm9yIGVhY2ggZ3JvdXAsIGFuZCBvdmVybGF5IHRoZSAkel97aSxqfSQgdmFsdWVzLCBmb3IgdGhlIGZpcnN0IGhhbmRmdWwgb2YgZnJhZ21lbnRzOgpgYGB7cn0KenMgPC0gbWVsdCh6X2lqWzE6NixdLCB2YWx1ZS5uYW1lID0gImFidW5kYW5jZSIpICMgZmlyc3Qgc2l4IHBlcHRpZGVzCnpzJGdyb3VwIDwtIGdzdWIoIiguKj8pXFwtXFxkIiwgIlxcMSIsIHpzJHNhbXBsZUlEKQptdXMgPC0gbWVsdChtdV9palsxOjYsXSwgdmFsdWUubmFtZSA9ICJhYnVuZGFuY2UiKQptdXMgPC0gbWVyZ2UobXVzLCBtZWx0KHZhcl9pWzE6Nl0sIHZhbHVlLm5hbWUgPSAidmFyIiksIGJ5Lng9InBlcHRpZGUiLCBieS55PTApCmdncGxvdCh6cywgYWVzKHg9Z3JvdXAsIHk9YWJ1bmRhbmNlLCBjb2xvcj1zYW1wbGVJRCkpICsgZ2VvbV9wb2ludCgpICsgCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArCiAgZ2VvbV9jcm9zc2JhcihkYXRhPW11cywgbWFwcGluZyA9IGFlcyh5PWFidW5kYW5jZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHltaW49YWJ1bmRhbmNlLTIqc3FydCh2YXIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeW1heD1hYnVuZGFuY2UrMipzcXJ0KHZhcikpLAogICAgICAgICAgICAgICAgY29sb3I9SSgiYmxhY2siKSkgKwogIGZhY2V0X3dyYXAofnBlcHRpZGUpICsgdGhlbWVfYncoKQpgYGAKCk9LLCBzbyB3aXRoIHRoYXQgY29uZmlybWVkLCBsZXQncyByZW1lbWJlciB0aGF0IHRoaXMgaXMgYSByYXRoZXIgaWRlYWxpemVkIGRhdGFzZXQgd2hlcmUgZGF0YSBmcm9tIGV2ZXJ5IHNhbXBsZSBjb21lcyBhbHJlYWR5ICJwcmUtbm9ybWFsaXplZCIgdG8gYWxsIHRoZSBvdGhlcnMuIExldCdzIHNlZSB3aGF0IEkgbWVhbjoKYGBge3J9CmdncGxvdChtZWx0KHpfaWosIHZhbHVlLm5hbWUgPSAiYWJ1bmRhbmNlIiksCiAgICAgICBhZXMoeD1zYW1wbGVJRCwgeT1hYnVuZGFuY2UsIGNvbG9yPXNhbXBsZUlEKSkgKyBnZW9tX3Zpb2xpbigpICsgCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArIHRoZW1lX2J3KCkKYGBgCkluIHJlYWwsICJyYXciIGRhdGEsIHRoZSBkaXN0cmlidXRpb24gb2YgYWJ1bmRhbmNlcyBpbiBlYWNoIHNhbXBsZSB3b3VsZCB2YXJ5IGEgbGl0dGxlIGJpdCwgcmVmbGVjdGluZyBkaWZmZXJlbmNlcyBpbiBzYW1wbGUgY29uY2VudHJhdGlvbiByZWxhdGl2ZSB0byBlYWNoIG90aGVyIChlc3BlY2lhbGx5IGluIGEgbXVsdGlwbGV4ZWQgZXhwZXJpbWVudCkuIEJldHdlZW4tc2FtcGxlIG5vcm1hbGl6YXRpb24gdGVjaG5pcXVlcyAocXVhbnRpbGUgbm9ybWFsaXplLCBtZWRpYW4gbm9ybWFsaXphdGlvbiwgZXRjLikgYXJlIHVzZWQgdG8gImFsaWduIiB0aGUgZGlzdHJpYnV0aW9ucywgYnV0IHdlIGRvbid0IG5lZWQgdG8gZG8gc3VjaCBhIHN0ZXAgaGVyZS4gSG93ZXZlciwgYXMgZGlzY3Vzc2VkIGluIHRoZSBwcm9EQSBwYXBlciwgcmVhbCAicmF3IiBkYXRhIF9kb2VzXyBnZW5lcmFsbHkgbmVlZCB0byBiZSBub3JtYWxpemVkLCBhbmQgdGhhdCBub3JtYWxpemF0aW9uIHN0ZXAgY2FuIChhbmQgZG9lcykgaW50cm9kdWNlIGRpc3RvcnRpb25zIHdoZW4gdGhlICJsb3dlciBlbmQiIG9mIHRoZSBhYnVuZGFuY2UgZGlzdHJpYnV0aW9uIGhhcyBhIGhpZ2hlciBkcm9wb3V0IHJhdGUsIGxlYWRpbmcgdG8gbW9yZSBkcm9wb3V0IE5BcyAob3IgemVyb2VzKSAtLSBlc3BlY2lhbGx5IHdoZW4gdGhlIGVudGlyZSBkaXN0cmlidXRpb24gZm9yIGEgZ2l2ZW4gc2FtcGxlIGlzIHNoaWZ0ZWQgbG93ZXIsIGR1ZSB0byBhbiBvdmVyYWxsIGxvd2VyIHJlbGF0aXZlIHNhbXBsZSBhYnVuZGFuY2UgKG1vcmUgZHJvcG91dHMvemVyb2VzIHRoYW4gaW4gb3RoZXIgc2FtcGxlcykuCgpPSywgbm93IHRvIHNpbXVsYXRlIHRoZSBkcm9wb3V0cyBpbiBlYWNoIHNhbXBsZS4gVGhlIHByb0RBIG1vZGVsIHVzZXMgYSBzaWdtb2lkIGN1cnZlICh1c2luZyB0aGUgY29tcGxlbWVudCBvZiB0aGUgTm9ybWFsIENERiBmdW5jdGlvbiBmb3IgY29udmVuaWVuY2UsIGVxdWF0aW9uIDIpLCB3aXRoIHBhcmFtZXRlcnMgJFxyaG9faiQgKGluZmxlY3Rpb24gcG9pbnQpIGFuZCAkXHpldGFfaiQgKHNjYWxlKSB0byByZXByZXNlbnQgdGhlIGRyb3BvZmYgcHJvYmFiaWxpdHksIGF0IGFueSBnaXZlbiBhYnVuZGFuY2UgdmFsdWUuIExldCdzIGxvb2sgYXQgYSBmZXcgZXhhbXBsZSBkcm9wb3V0IGN1cnZlcyBmb3IgYSBjb3VwbGUgZGlmZmVyZW50IHBhcmFtZXRlciBjaG9pY2VzOgpgYGB7cn0KaXBub3JtIDwtIGZ1bmN0aW9uKC4uLikgewogIDEgLSBwbm9ybSguLi4pCn0KZ2dwbG90KCkgKwogIHN0YXRfZnVuY3Rpb24oZnVuID0gaXBub3JtLAogICAgICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIsCiAgICAgICAgICAgICAgICBhcmdzID0gbGlzdChtZWFuID0gMSwgc2QgPSAxKSkgKwogIHN0YXRfZnVuY3Rpb24oZnVuID0gaXBub3JtLAogICAgICAgICAgICAgICAgY29sb3I9InJlZCIsCiAgICAgICAgICAgICAgICBhcmdzID0gbGlzdChtZWFuPTEwLCBzZD01KSkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD0xLCBjb2xvcj0iYmx1ZSIsIGxpbmV0eXBlPTIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MTAsIGNvbG9yPSJyZWQiLCBsaW5ldHlwZT0yKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTAuNSwgY29sb3I9ImdyZXkiLCBsaW5ldHlwZT0yKSArCiAgeGxpbSgtMTAsIDMwKSArIHhsYWIoImFidW5kYW5jZSIpICsgeWxhYigiZHJvcG91dCBwcm9iYWJpbGl0eSIpICsgdGhlbWVfYncoKQpgYGAKRnJvbSB0aGVzZSB0d28gZXhhbXBsZSBkcm9wb3V0IGN1cnZlcywgd2UgY2FuIHNlZSB0aGF0IHRoZSBpbmZsZWN0aW9uIHBvaW50ICgkXHJob19qJCwgc3BlY2lmaWVkIGFzIHRoZSBtZWFuIHRvIGBwbm9ybWAgTm9ybWFsIENERiBmdW5jdGlvbikgaXMgd2hlcmUgdGhlIHByb2JhYmlsaXR5IGlzIGV4YWN0bHkgMC41IChzZWUgZG90dGVkIGxpbmVzKTsgYW5kIHRoZSBzY2FsZSAoJFx6ZXRhX2okLCBzcGVjaWZpZWQgYXMgdGhlIHMuZC4gdG8gYHBub3JtYCBkZWZpbmVzIHRoZSBzbG9wZSBvZiB0aGUgY3VydmUsIGhvdyAic2hhcnAiIHRoZSBkZW1hcmNhdGlvbiBpcyBiZXR3ZWVuIGhpZ2ggYW5kIGxvdyBkcm9wb3V0IHByb2JhYmlsaXR5IG92ZXIgYSBnaXZlbiByYW5nZSBvZiBhYnVuZGFuY2VzKS4KCk9LLCB3aXRoIHNvbWUgaW50dWl0aW9uIGluIGhhbmQgbm93LCB3ZSBjYW4gc3RhcnQgdG8gcmVhc29uIGFib3V0IGhvdyB0aGUgZHJvcG91dCBjdXJ2ZXMgbWlnaHQgZGlmZmVyIGZvciBlYWNoIHNhbXBsZS4gSW4gdGhlIHByb0RBIG1vZGVsLCB0aGUgc2FtcGxlLXNwZWNpZmljIGluZmxlY3Rpb24gcG9pbnRzIGFuZCBzY2FsZXMgZG9uJ3QgaGF2ZSBhbnkgY29tbW9uLCBzaGFyZWQgcGFyZW50IGRpc3RyaWJ1dGlvbiwgYnV0IGFyZSBlc3RpbWF0ZWQgaW5kaXZpZHVhbGx5IGZvciBlYWNoIHNhbXBsZSwgYmFzZWQgb24gYW4gb3B0aW1pemF0aW9uIG9mIHRoZSBsaWtlbGlob29kIGZ1bmN0aW9uIGZvciBlYWNoIHNhbXBsZTsgc2luY2UgdGhlcmUgYXJlIHNvIG1hbnkgcGVwdGlkZSBmcmFnbWVudHMgdG8gaW5mb3JtIHRoYXQgcGFyYW1ldGVyIGVzdGltYXRpb24sIHRoYXQgaXMgcXVpdGUgY29tcHV0YXRpb25hbGx5IGVmZmljaWVudC4gSG93ZXZlciwgd2UndmUgYmVlbiBlbnRpcmVseSBwcm9iYWJpbGlzdGljIHNvIGZhciBpbiB0aGlzIHNpbXVsYXRpb24sIHdpdGggZXZlcnkgdmFsdWUgb2YgaW50ZXJlc3QgZGVyaXZlZCBhcyBhIHJhbmRvbSBkcmF3IGZyb20gc29tZSBwYXJlbnQgZGlzdHJpYnV0aW9uLCBzbyBsZXQncyBzdGljayB3aXRoIHRoYXQgdGhlbWUuIEdpdmVuIHRoYXQgd2Ugc3BlY2lmaWVkIHRoZSAiZ3JhbmQgbWVhbiIgJFxtdV8wJCBvZiBhbGwgcGVwdGlkZSBhYnVuZGFuY2VzIGF0IGByIG11XzBgLCB3aXRoIGEgInJhbmdlIiAkXHNpZ21hXzBeMiQgb2YgYHIgc2RfMF4yYCwgbGV0J3Mgc2F5IHdlIHNldCB0aGUgImF2ZXJhZ2UiIGluZmxlY3Rpb24gcG9pbnQgKHdoZXJlIHRoZSBkcm9wb3V0IHByb2JhYmlsaXR5IHdpbGwgYmUgMC41KSwgJFxyaG9fMCQsIG92ZXIgYWxsIHNhbXBsZXMgdG8gYmUgJFxtdV8wIC0gNFxzaWdtYV8wJCwgNCB0aW1lcyB0aGUgU0Q7IGFuZCB0aGVuIGxldCBlYWNoIHNhbXBsZSdzICRccmhvX2okIHZhcnkgZnJvbSB0aGlzIGJ5ICRcc2lnbWFfe1xyaG9fMH1eMiQuIFNpbWlsYXJseSwgbGV0J3MgbGV0IHRoZSBhdmVyYWdlIHNjYWxlLCAkXHpldGFfMCQgYmUgYSBoeXBlcnBhcmFtZXRlciwgYW5kIGRyYXcgZWFjaCBzYW1wbGUncyAkXHpldGFfaiQgZnJvbSB0aGUgSW52Q2hpU3F1YXJlIGRpc3RyaWJ1dGlvbiBhcyB3ZSBkaWQgZWFybGllciBmb3IgdGhlIGFuYWxhZ291cyBwZXB0aWRlIHZhcmlhbmNlczoKCmBgYHtyfQpyaG9fMCA8LSBtdV8wIC0gNCAqIHNkXzAKc2lnbWFfcmhvXzAgPC0gMS4yCnJob19qIDwtIHJub3JtKG5jb2woel9paiksIHJob18wLCBzaWdtYV9yaG9fMCkKbmFtZXMocmhvX2opIDwtIGNvbG5hbWVzKHpfaWopCgp6ZXRhXzAgPC0gMTAKemV0YV90YXVfMCA8LSAzCnpldGFfaiA8LSByaW52Y2hpc3EobmNvbCh6X2lqKSwgemV0YV8wLCB6ZXRhX3RhdV8wKQpuYW1lcyh6ZXRhX2opIDwtIGNvbG5hbWVzKHpfaWopCgpnZ3Bsb3QoKSArCiAgbGFwcGx5KDE6bGVuZ3RoKHpldGFfaiksIGZ1bmN0aW9uIChpKSB7CiAgICAgc3RhdF9mdW5jdGlvbihmdW4gPSBpcG5vcm0sCiAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKGNvbG9yPWNvbG5hbWVzKHpfaWopW2ldKSwKICAgICAgICAgICAgICAgICAgIGFyZ3MgPSBsaXN0KG1lYW49cmhvX2pbaV0sIHNkPXpldGFfaltpXSkpCiAgICB9KSArCiAgc2NhbGVfY29sb3JfYnJld2VyKG5hbWU9InNhbXBsZUlEIiwgcGFsZXR0ZT0iU2V0MSIpICsKICB4bGltKC0xMCwgMzApICsgeGxhYigiYWJ1bmRhbmNlIikgKyB5bGFiKCJkcm9wb3V0IHByb2JhYmlsaXR5IikgKyB0aGVtZV9idygpCgpgYGAKT0ssIGdyZWF0OyB3ZSd2ZSBnb3QgdGhlIHVuZGVybHlpbmcgZHJvcG91dCBjdXJ2ZXMsIG9uZSBmb3IgZWFjaCBzYW1wbGUuIE5vdyB3ZSBjYW4gc2ltdWxhdGUgd2hpY2ggb2YgdGhlICR6X3tpLGp9JCB2YWx1ZXMgd2lsbCBiZSBvYnNlcnZlZCAobm90IGEgZHJvcG91dCkgYW5kIHdoaWNoIHdpbGwgYmUgIk5BIiAoaXMgYSBkcm9wb3V0KSwgYnkgZmxpcHBpbmcgYSBjb2luIHdpdGggdGhlIHByb2JhYmlsaXR5IG9mIGRyb3BvdXQgZGVmaW5lZCBmb3IgZWFjaCAkel97aSxqfSQgYWJ1bmRhbmNlIHZhbHVlIGdpdmVuIGJ5IHRoZSBkcm9wb3V0IGN1cnZlIGZvciBzYW1wbGUgJGokLiBUaGlzIHRoZSAiZHJvcG91dCBtYXRyaXgiLCAkZF97aSxqfSQuCgpgYGB7cn0KZF9paiA8LSB6X2lqCmRfaWpbLF0gPC0gMApmb3IgKGogaW4gMTpuY29sKGRfaWopKSB7CiAgZF9palssal0gPC0gcmJlcm4obj1ucm93KGRfaWopLCBwcm9iPWlwbm9ybSh6X2lqWyxqXSwgcmhvX2pbal0sIHpldGFfaltqXSkpCn0KcXBsb3QocmhvX2osIDEwMCAqIGNvbFN1bXMoZF9paikvbnJvdyhkX2lqKSwgY29sb3I9emV0YV9qLCBzaXplPUkoNSkpICsKICBnZW9tX2xhYmVsKGFlcyhsYWJlbD1uYW1lcyh6ZXRhX2opKSkgKyAKICBzY2FsZV95X2xvZzEwKCkgKwogIHlsYWIoIiUgb2YgZHJvcG91dHMgaW4gc2FtcGxlIikgKyB0aGVtZV9idygpCmBgYAoKRnJvbSB0aGUgYWJvdmUsIHdlIHNlZSB0aGF0IHRoZSByYW5nZSBvZiBkcm9wb3V0ICUgaW4gZWFjaCBzYW1wbGUgY29ycmVsYXRlcyB3aXRoIHRoZSAkXHJob19qJCBpbmZsZWN0aW9uIHBvaW50cyAtLSB0aGUgaGlnaGVyIChyaWdodC1zaGlmdGVkKSB0aGUgaW5mbGVjdGlvbiBwb2ludCwgdGhlIG1vcmUgYWJ1bmRhbmNlIHZhbHVlcyBhcmUgbG9zdCBkdWUgdG8gZHJvcG91dDsgYW5kIGFsc28sIGZvciBhIGdpdmVuICRccmhvX2okIHZhbHVlLCB0aGUgaGlnaGVyIHRoZSAkXHpldGFfaiQgdmFsdWUsIHRoZSBzaGFsbG93ZXIgdGhlIGRyb3BvZmYgc2xvcGUsIGFuZCBzbyBldmVuIG1vcmUgYWJ1bmRhbmNlcyBkcm9wb3V0LgoKTm93IHdlIGhhdmUgZXZlcnl0aGluZyB3ZSBuZWVkIGZyb20gQXBwZW5kaXggZXF1YXRpb24gKDEpIHRvIHByb2R1Y2UgYSBmaW5hbCwgc2ltdWxhdGVkIGRhdGFzZXQgb2YgZXhwZXJpbWVudGFsbHkgb2JzZXJ2ZWQgYWJ1bmRhbmNlcywgJHlfe2ksan0kLgoKYGBge3J9CiMgc3RhcnQgYnkganVzdCBjb3B5aW5nIHpfaWogaW50byB5X2lqOgp5X2lqIDwtIHpfaWoKCiMgbm93IGRyb3BvdXQgdGhlIHZhbHVlcyBhY2NvcmRpbmcgdG8gZF9pajoKZm9yIChqIGluIDE6bmNvbCh5X2lqKSkgewogIHlfaWpbZF9palssal0gPT0gMSwgal0gPC0gTkEKfQoKcHJpbnQoaGVhZCh5X2lqKSwgZGlnaXRzPTQpCmBgYAoKSW4gdGhlIGZpcnN0IHNpeCBmcmFnbWVudHMgYWJvdmUsIHdlIGNhbiBhbHJlYWR5IHNlZSBhIGhhbmRmdWwgb2YgTkEncyB0aGF0IHJlcHJlc2VudCBzaW11bGF0ZWQgZHJvcG91dHMuCgpPSywgbm93IHdlIGNhbiB0ZXN0IHByb0RBIHVzaW5nIG91ciBzaW11bGF0ZWQgZGF0YSwgZm9yIHdoaWNoIHdlIGtub3cgdGhlIHRydWUgdmFsdWVzIGZvciBlYWNoIHNpbmdsZSBwYXJhbWV0ZXIgb2YgaW50ZXJlc3QsIGFuZCB3ZSBjYW4gc2VlIGhvdyB3ZWxsIHByb0RBIGlzIGFibGUgdG8gcmVjb3ZlciB3aGF0IHdlJ3ZlIHNldHVwOgoKYGBge3J9CmZpdCA8LSBwcm9EQSh5X2lqLCBkZXNpZ24gPSBmYWN0b3IocmVwKGdyb3VwcywgZWFjaD1ucmVwKSksIHZlcmJvc2U9VCkKYGBgClNvLCBwZXJoYXBzIG5vdCB0b28gdW5zdXJwcmlzaW5nbHksIHdoZW4gd2UgdXNlIHByb0RBIHRvIGdlbmVyYXRlIGEgZml0IHdpdGggZGF0YSB0aGF0IGV4YWN0bHkgbWF0Y2hlcyB0aGUgbW9kZWxpbmcgZXhwZWN0YXRpb25zLCBpdCBzZWVtcyB0byBkbyBhIHByZXR0eSBnb29kIGpvYiEgTGV0J3MgbG9vayBhdCB0aGUgdmFyaW91cyBoeXBlcnBhcmFtZXRlcnMgZmlyc3Q6CmBgYHtyfQpocHMgPC0gZml0JGh5cGVyX3BhcmFtZXRlcnMKa25pdHI6OmthYmxlKAogIGRhdGEuZnJhbWUoCiAgICBuYW1lPWMoImdyYW5kIG1lYW4iLAogICAgICAgICAgICJncmFuZCByYW5nZSIsCiAgICAgICAgICAgIm5vcm1hbGl0eSBwYXJhbWV0ZXIiLAogICAgICAgICAgICJ2YXJpYW5jZSBzY2FsZSIsCiAgICAgICAgICAgInZhcmlhbmNlIHNoYXBlIiksCiAgICB2YWx1ZXM9YyhtdV8wLCBzZF8wXjIsIGRmX2xvYywgdGF1XzBeMiwgZGZfMCksCiAgICBlc3RpbWF0ZT1jKGhwcyRsb2NhdGlvbl9wcmlvcl9tZWFuLAogICAgICAgICAgICAgICBocHMkbG9jYXRpb25fcHJpb3Jfc2NhbGUsCiAgICAgICAgICAgICAgIGhwcyRsb2NhdGlvbl9wcmlvcl9kZiwgIyBnaXZlbiBhcyBhIGZpeGVkIGNvbnN0YW50LCBub3QgZXN0aW1hdGVkCiAgICAgICAgICAgICAgIGhwcyR2YXJpYW5jZV9wcmlvcl9zY2FsZSwKICAgICAgICAgICAgICAgaHBzJHZhcmlhbmNlX3ByaW9yX2RmKSksIGZvcm1hdD0icGlwZSIpCmBgYAoKV2hpbGUgdGhlIHBhcmFtZXRlcnMgKGFuZCByYW5kb20gc2VlZCkgb2YgdGhpcyBub3RlYm9vayBjb3VsZCBjaGFuZ2UsIGF0IGxlYXN0IHdpdGggdGhlIGN1cnJlbnQgKD8pIHZhbHVlcywgd2Ugc2VlIHRoYXQgbmVhcmx5IGFsbCBvZiB0aGUgaHlwZXJwYXJhbWV0ZXJzIG1hdGNoIHZlcnkgd2VsbCwgd2l0aCB0aGUgZXhjZXB0aW9uIG9mIHRoZSAiZ3JhbmQgcmFuZ2UiLCBvciAkXHNpZ21hXzBeMiQsIHdoaWNoIHByb0RBIGhhcyBvdmVyLWVzdGltYXRlZCBhcyBsYXJnZXIgdGhhbiB0aGUgdHJ1ZSBkaXN0cmlidXRlZCByYW5nZSBvZiBncm91cC13aXNlIG1lYW4gdmFsdWVzOyBJIGV4cGVjdCB0aGlzIGlzIGFuIHVuZHVlIGluZmx1ZW5jZSBvZiB0aGUgZHJvcG91dCB2YWx1ZXMgdGhlbXNlbHZlcywgd2hpY2ggd2lsbCB0ZW5kIHRvICJpbmZsYXRlIiB0aGUgZXhwZWN0ZWQgdmFyaWFuY2Ugb2YgdGhlIGdyb3VwIG1lYW5zLCBhdCB0aGUgYm90dG9tL2xlZnQgZW5kLCBldmVuIHRob3VnaCB0aGUgdG9wL3JpZ2h0IGVuZCBpcyBhY2N1cmF0ZWx5IGFuZCBleHBsaWN0bHkgcmVwcmVzZW50ZWQgaW4gdGhlIGRhdGEgKHRoaXMgYW1vdW50cyB0byBhIHN1Z2dlc3Rpb24gZm9yIHRoZSBwcm9EQSBhdXRob3JzIHRvIGNvbnNpZGVyIGVzdGltYXRpbmcgdGhlIHZhcmlhbmNlIGZyb20gYSBsZWZ0LXRydW5jYXRlZCBkaXN0cmlidXRpb24gb2YgZ3JvdXAgbWVhbnMsIHRvIGF2b2lkIHRoaXMgYXJ0aWZhY3QpLgoKTmV4dCwgbGV0J3MgY2hlY2tvdXQgdGhlIGRyb3BvdXQgY3VydmUgcGFyYW1ldGVyczoKYGBge3J9CmdncGxvdChyYmluZChkYXRhLmZyYW1lKHZhcmlhYmxlPSJkcm9wb3V0IHBvc2l0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWU9cmhvX2osCiAgICAgICAgICAgICAgICAgICAgICAgIGVzdGltYXRlPWhwcyRkcm9wb3V0X2N1cnZlX3Bvc2l0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVJRCA9IG5hbWVzKHJob19qKSksCiAgICAgICAgICAgICBkYXRhLmZyYW1lKHZhcmlhYmxlPSJkcm9wb3V0IHNjYWxlIiwKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWU9emV0YV9qLAogICAgICAgICAgICAgICAgICAgICAgICBlc3RpbWF0ZT0taHBzJGRyb3BvdXRfY3VydmVfc2NhbGUsCiAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZUlEID0gbmFtZXMoemV0YV9qKSkpLAogICAgICAgYWVzKHg9dmFsdWUsIHk9ZXN0aW1hdGUpKSArIAogIGdlb21fYWJsaW5lKHNsb3BlPTEsIGludGVyY2VwdD0wKSArCiAgc3RhdF9zbW9vdGgobWV0aG9kPSJsbSIsIGZvcm11bGEgPSB5IH4geCkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yPXNhbXBsZUlEKSkgKwogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXM9ImZyZWUiKSArICB0aGVtZV9idygpCmBgYApJbnRlcmVzdGluZyEgV2hpbGUgYm90aCB0aGUgbG9jYXRpb24gKCRccmhvJCkgYW5kIHNjYWxlICgkXHpldGEkKSBwYXJhbWV0ZXJzIGFyZSB2ZXJ5IHN0cm9uZ2x5IGNvcnJlbGF0ZWQsIHRoZSBsb2NhdGlvbiBpcyB2ZXJ5IHNsaWdodGx5IHVuZGVyLWVzdGltYXRlZCAodGhvdWdoIG5vdCBzdGF0aXN0aWNhbGx5IHNpZ25pY2FudCBpbiB0aGlzIGNhc2UpLCB3aGlsZSB0aGUgc2NhbGUgaXMgZGlzdGluY3RseSBhbmQgc3lzdGVtYXRpY2FsbHkgX292ZXItZXN0aW1hdGVkXyBpbiBhbGwgc2FtcGxlcy4gT2ZmIHRoZSB0b3Agb2YgbXkgaGVhZCwgSSBkb24ndCBoYXZlIGFueSByYXRpb25hbGUgZm9yIHRoaXMgYmVoYXZpb3IsIHNvIEknbSBnb2luZyB0byBsZWF2ZSB0aGlzIGFzIGEgcXVlc3Rpb24gZm9yIHRoZSBhdXRob3JzICghPyEpCgpPSywgc28gbW9zdCBpbXBvcnRhbnRseSwgbGV0J3MgbG9vayBhdCBob3cgYWNjdXJhdGUgcHJvREEncyBlc3RpbWF0aW9uIG9mIHRoZSBncm91cCBtZWFucyBlbmRlZCB1cCwgaW4gdGhlIGZhY2Ugb2YgYWxsIHRoZXNlIGRyb3BvdXRzLCBhbmQgKG1vc3RseSkgY29ycmVjdCBoeXBlcnBhcmFtZXRlciBlc3RpbWF0aW9uczoKCmBgYHtyfQpjb2VmIDwtIGZpdCRjb2VmZmljaWVudHMKbmFtZXMoZGltbmFtZXMoY29lZikpIDwtIGMoInBlcHRpZGUiLCAiZ3JvdXAiKQpkdGFsbCA8LSBtZWx0KGRfaWosIHZhbHVlLm5hbWUgPSAiZHJvcCIpCmR0YWxsJGdyb3VwIDwtIGdzdWIoIiguKj8pXFwtXFxkKyIsICJcXDEiLCBkdGFsbCRzYW1wbGVJRCkKCmRyb3BkZiA8LSBkZHBseShkdGFsbCwgLihwZXB0aWRlLCBncm91cCksIGZ1bmN0aW9uKGR4KSB7CiAgZGF0YS5mcmFtZShkcm9wbnVtID0gc3VtKGR4JGRyb3ApKQp9KQoKc2VzIDwtIG1hdHJpeCh1bmxpc3QobGFwcGx5KGxhcHBseShmaXQkY29lZmZpY2llbnRfdmFyaWFuY2VfbWF0cmljZXMsIGRpYWcpLCBzcXJ0KSksCiAgICAgICAgICAgICAgbmNvbD0zLCBieXJvdz1UKQpjb2xuYW1lcyhzZXMpIDwtIGNvbG5hbWVzKGZpdCRjb2VmZmljaWVudF92YXJpYW5jZV9tYXRyaWNlc1tbMV1dKQpzZXMgPC0gYXMuZGF0YS5mcmFtZShzZXMpCnNlcyRwZXB0aWRlIDwtIHJvd25hbWVzKGZpdCRjb2VmZmljaWVudHMpCnNlcyRsb3dxdCA8LSBxdCgwLjAyNSwgZml0JGZlYXR1cmVfcGFyYW1ldGVycyRkZikKc2VzJGhpZ2hxdCA8LSBxdCgwLjk3NSwgZml0JGZlYXR1cmVfcGFyYW1ldGVycyRkZikKY2lzIDwtIG1lbHQoc2VzLAogICAgICAgICAgICBpZD1jKCJwZXB0aWRlIiwgImhpZ2hxdCIsICJsb3dxdCIpLAogICAgICAgICAgICB2YXJpYWJsZS5uYW1lID0gImdyb3VwIiwKICAgICAgICAgICAgdmFsdWUubmFtZSA9ICJzZXMiKQpjaXMkbG93IDwtIGNpcyRsb3dxdCAqIGNpcyRzZXMKY2lzJGhpZ2ggPC0gY2lzJGhpZ2hxdCAqIGNpcyRzZXMKCmRmIDwtIG1lcmdlKGRyb3BkZiwgbWVyZ2UobWVsdChjb2VmLCB2YWx1ZS5uYW1lID0gImVzdGltYXRlIiksIG1lbHQobXVfaWosIHZhbHVlLm5hbWUgPSAidmFsdWUiKSkpCmRmIDwtIG1lcmdlKGRmLCBjaXMpCgpkZiRlc3Rsb3cgPC0gZGYkZXN0aW1hdGUgKyBkZiRsb3cKZGYkZXN0aGlnaCA8LSBkZiRlc3RpbWF0ZSArIGRmJGhpZ2gKZGYkZXN0cmFuZ2UgPC0gZGYkZXN0aGlnaCAtIGRmJGVzdGxvdwoKZ2dwbG90KGRmLCBhZXMoeD12YWx1ZSwgeT1lc3RpbWF0ZSwgc2hhcGU9Z3JvdXAsIGNvbG9yPWZhY3Rvcihkcm9wbnVtKSkpICsKICBnZW9tX3BvaW50KCkgKyBnZW9tX2FibGluZShzbG9wZT0xLCBpbnRlcmNlcHQ9MCkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihuYW1lPSIjIGRyb3BvdXRzIiwgcGFsZXR0ZT0iU2V0MiIpICsKICBmYWNldF93cmFwKH5kcm9wbnVtKSArIHRoZW1lX2J3KCkKYGBgCgpBZ2FpbiwgaW50ZXJlc3RpbmchIFdoZW4gdGhlIG51bWJlciBvZiBkcm9wb3V0cyBpbiBhIGdyb3VwIGlzIDAsIDEsIG9yIGV2ZW4gMiwgdGhlbiBwcm9EQSBkb2VzIGEgcHJldHR5IHJlbWFya2FibGUgam9iIHJlY292ZXJpbmcgdGhlIHRydWUgdmFsdWUgb2YgdGhlIGdyb3VwIG1lYW4uIEJ1dCB3aGVuIGFsbCBvZiB0aGUgcmVwbGljYXRlcyBpbiBhIGdyb3VwIGFyZSBkcm9wb3V0cywgd2UncmUgbGVmdCB3aXRoIGNvZWZmaWNpZW50IGVzdGltYXRlcyB0aGF0IGFyZSBhbGwgfjEyLiBUaGUgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgcmFuZ2VzIGxvb2sgbGlrZSB0aGlzOgoKYGBge3J9CmdncGxvdChkZiwgYWVzKHg9ZXN0cmFuZ2UsIGZpbGw9ZmFjdG9yKGRyb3BudW0pKSkgKwogIHN0YXRfYmluKGJpbnM9NTApICsgCiAgc2NhbGVfY29sb3JfYnJld2VyKG5hbWU9IiMgZHJvcG91dHMiLCBwYWxldHRlPSJTZXQyIikgKwogIGZhY2V0X3dyYXAofmRyb3BudW0sIHNjYWxlcz0iZnJlZV95IiwgbmNvbD0xKSArIHRoZW1lX2J3KCkgKwogIHhsYWIoIndpZHRoIG9mIDk1JSBjb25maWRlbmNlIGludGVydmFsIikKYGBgCgpTbywgYXMgZXhwZWN0ZWQ6IHRpZ2h0ZXIgaW50ZXJ2YWwgcmFuZ2VzIGZvciBncm91cHMgaGF2aW5nIGFsbCBvYnNlcnZhdGlvbnM7IHdpZGVyIHJhbmdlcyB3aXRoIGxlc3Mgb2JzZXJ2YXRpb25zLCBhbmQgZXNzZW50aWFsbHkgZml4ZWQgKHdpZGUpIGludGVydmFsIHdoZW4gbm8gb2JzZXJ2YXRpb25zIGFyZSBtYWRlLgoKTGFzdGx5LCBsZXQncyBzZWUgaG93IHdlbGwgcHJvREEgd2FzIGFibGUgdG8gZXN0aW1hdGUgdmFyaWFuY2VzIGZvciBlYWNoIHBlcHRpZGU6CmBgYHtyfQp2YXJkZiA8LSBkYXRhLmZyYW1lKHZhbHVlID0gdmFyX2ksCiAgICAgICAgICAgICAgICAgICAgZXN0aW1hdGUgPSBmaXQkZmVhdHVyZV9wYXJhbWV0ZXJzJHMyLAogICAgICAgICAgICAgICAgICAgIG1pc3NpbmcgPSBuY29sKHlfaWopIC0gZml0JGZlYXR1cmVfcGFyYW1ldGVycyRuX29icykKCmdncGxvdCh2YXJkZiwgYWVzKHg9dmFsdWUsIHk9ZXN0aW1hdGUpKSArIHN0YXRfYmluaGV4KCkgKwogIGdlb21fYWJsaW5lKHNsb3BlPTEsIGludGVyY2VwdD0wKSArIHRoZW1lX2J3KCkgKwogIHhsYWIoInRydWUgdmFyaWFuY2UiKSArIHlsYWIoInByb0RBIGVzdGltYXRlZCB2YXJpYW5jZSIpCgpgYGAKVGhlIG1ham9yaXR5IG9mIHZhcmlhbmNlcyBhcmUgY29ycmVjdGx5IGVzdGltYXRlZCwgYnV0IGEgaGFuZGZ1bCBvZiBwZXB0aWRlcyBoYXZpbmcgdGhlIGxhcmdlc3QgdmFyaWFuY2VzIHJlbWFpbiB1bmRlci1lc3RpbWF0ZWQsIHBvdGVudGlhbGx5IGluZmxhdGluZyBhbnkgZG93bnN0cmVhbSBjb250cmFzdCBzdGF0aXN0aWNzLgoKIyBlZmZlY3RzIG9mIG5vcm1hbGl6YXRpb24gd2l0aCBkcm9wb3V0cwoKV2Ugb2JzZXJ2ZWQgZWFybGllciB0aGF0IHRoZSBzaW11bGF0ZWQgZGF0YSBpcyBhbHJlYWR5IHBlcmZlY3RseSBub3JtYWxpemVkLCBhdCBsZWFzdCBwcmlvciB0byBoYXZpbmcgcmFuZG9tIGxvdy1hYnVuZGFuY2UgdmFsdWVzIHJlbW92ZWQgZHVlIHRvIGRyb3BvdXQ7IGFuZCBlYWNoIHNhbXBsZSBoYXMgc2xpZ2h0bHkgZGlmZmVyZW50IGRyb3BvdXQgY3VydmVzLCBzbyBub3cgdGhlIGRpc3RyaWJ1dGlvbnMgbWF5IG5vdCBiZSBzbyBwZXJmZWN0bHkgY29tcGFyYWJsZToKCmBgYHtyfQpnZ3Bsb3QobWVsdCh5X2lqLCB2YWx1ZS5uYW1lID0gImFidW5kYW5jZSIpLAogICAgICAgYWVzKHg9c2FtcGxlSUQsIHk9YWJ1bmRhbmNlLCBjb2xvcj1zYW1wbGVJRCkpICsgZ2VvbV92aW9saW4oKSArIAogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTZXQxIikgKyB0aGVtZV9idygpCmBgYAoKV2hhdCBoYXBwZW5zIGlmIHdlIHF1YW50aWxlIG5vcm1hbGl6ZSB0aGUgb2JzZXJ2YXRpb25zLCBhbmQgdGhlbiBjb21wYXJlIHRvIHRoZWlyIG9yaWdpbmFsIHZhbHVlcyAocmVtb3ZpbmcvaWdub3JpbmcgYW55IGRpZmZlcmVuY2UgbGVzcyB0aGFuIDAuMiB1bml0cywgZm9yIGdyYXBoaWNhbCBjbGFyaXR5KT8KCmBgYHtyfQpub3JteSA8LSBub3JtYWxpemVCZXR3ZWVuQXJyYXlzKHlfaWosIG1ldGhvZD0icXVhbnRpbGUiKQptZWR5IDwtIG1lZGlhbl9ub3JtYWxpemF0aW9uKHlfaWopCmRlbHRhcyA8LSBjb2xNZWFucyhtZWR5IC0geV9paiwgbmEucm09VCkKZGVsdGFzIDwtIGRhdGEuZnJhbWUoZGVsdGE9ZGVsdGFzLCBzYW1wbGVJRCA9IG5hbWVzKGRlbHRhcykpCmdncGxvdChzdWJzZXQobWVsdChub3JteSAtIHlfaWosIHZhbHVlLm5hbWUgPSAiZGVsdGEiKSwKICAgICAgICAgICAgICBhYnMoZGVsdGEpID49IDAuMiksCiAgICAgICBhZXMoeD1zYW1wbGVJRCwgeT1kZWx0YSwgY29sb3I9c2FtcGxlSUQpKSArIGdlb21fcXVhc2lyYW5kb20oKSArIAogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTZXQxIikgKyB0aGVtZV9idygpCmBgYApJdCBhcHBlYXJzLCBhdCBsZWFzdCBmb3IgdGhpcyBzaW11bGF0ZWQgZGF0YXNldCB0aGF0IGRvZXNuJ3QgaGF2ZSBncm9zcyBkaWZmZXJlbmNlcyBpbiBtZWFuIGFidW5kYW5jZXMgb3IgZHJvcG91dCByYXRlcywgdGhhdCBxdWFudGlsZSBub3JtYWxpemF0aW9uIGhhcyBhIGxhcmdlIGVmZmVjdCBvbiBvbmx5IGEgc21hbGwgbnVtYmVyIG9mIGZyYWdtZW50cy4gSWYgd2UgcmVmaXQgYHByb0RBYCB0byB0aGlzIChyZSlub3JtYWxpemVkIGRhdGEsIHdlJ2QgZXhwZWN0IHRvIHNlZSBzb21lIGRpZmZlcmVuY2VzOgpgYGB7cn0Kbm9ybWZpdCA8LSBwcm9EQShub3JteSwgZGVzaWduID0gZmFjdG9yKHJlcChncm91cHMsIGVhY2g9bnJlcCkpLCB2ZXJib3NlPVQpCmBgYAoKYGBge3J9Cm5vcm1jb2VmIDwtIG5vcm1maXQkY29lZmZpY2llbnRzCm5hbWVzKGRpbW5hbWVzKG5vcm1jb2VmKSkgPC0gYygicGVwdGlkZSIsICJncm91cCIpCgpjb2VmZGlmZiA8LSBub3JtY29lZiAtIGNvZWYKbmFtZXMoZGltbmFtZXMoY29lZmRpZmYpKSA8LSBjKCJwZXB0aWRlIiwgImdyb3VwIikKZ2dwbG90KHN1YnNldChtZWx0KGNvZWZkaWZmLCB2YWx1ZS5uYW1lID0gImRpZmYiKSwKICAgICAgICAgICAgICBhYnMoZGlmZikgPj0gMC4yKSwKICAgICAgIGFlcyh4PWdyb3VwLCB5PWRpZmYpKSArIGdlb21fcXVhc2lyYW5kb20oKSArCiAgdGhlbWVfYncoKSArIHlsYWIoImRpZmZlcmVuY2UgaW4gY29lZmZpY2llbnQgZXN0aW1hdGVzIikKCmdncGxvdChtZXJnZShtZWx0KGNvZWZkaWZmLCB2YWx1ZS5uYW1lPSJkaWZmIiksCiAgICAgICAgICAgICBtZWx0KGNvZWYsIHZhbHVlLm5hbWU9ImVzdGltYXRlIikpLAogICAgICAgYWVzKHg9ZXN0aW1hdGUsIHk9ZGlmZikpICsgZ2VvbV9wb2ludCgpICsgZmFjZXRfd3JhcCh+Z3JvdXApICsgdGhlbWVfYncoKSArCiAgeWxhYigiZGlmZmVyZW5jZSBpbiBjb2VmZmljaWVudCBlc3RpbWF0ZXMiKSArIHhsYWIoIm9yaWdpbmFsIGVzdGltYXRlIikKYGBgClBlcmhhcHMgYXMgZXhwZWN0ZWQsIHRoZSBmZXcgY29lZmZpY2llbnRzIHRoYXQgYXJlIGdyb3NzbHkgYWZmZWN0ZWQgYnkgdGhlIG5vcm1hbGl6YXRpb24gcHJvY2VkdXJlIGFyZSBjb25maW5lZCB0byB0aGUgKG1vc3RseSB1cHBlcikgZXh0cmVtZXMgb24gdGhlIGFidW5kYW5jZSBzY2FsZS4KCkJ1dCBsZXQncyBhbHNvIGxvb2sgYXQgd2hhdCBoYXBwZW5zIHRvIHRoZSBkcm9wb3V0IGN1cnZlIHBhcmFtZXRlcnM6CmBgYHtyfQpub3JtaHBzIDwtIG5vcm1maXQkaHlwZXJfcGFyYW1ldGVycwpnZ3Bsb3QocmJpbmQoZGF0YS5mcmFtZSh2YXJpYWJsZT0iZHJvcG91dCBwb3NpdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlPXJob19qLAogICAgICAgICAgICAgICAgICAgICAgICBlc3RpbWF0ZT1ub3JtaHBzJGRyb3BvdXRfY3VydmVfcG9zaXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZUlEID0gbmFtZXMocmhvX2opKSwKICAgICAgICAgICAgIGRhdGEuZnJhbWUodmFyaWFibGU9ImRyb3BvdXQgc2NhbGUiLAogICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZT16ZXRhX2osCiAgICAgICAgICAgICAgICAgICAgICAgIGVzdGltYXRlPS1ub3JtaHBzJGRyb3BvdXRfY3VydmVfc2NhbGUsCiAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZUlEID0gbmFtZXMoemV0YV9qKSkpLAogICAgICAgYWVzKHg9dmFsdWUsIHk9ZXN0aW1hdGUpKSArIAogIGdlb21fYWJsaW5lKHNsb3BlPTEsIGludGVyY2VwdD0wKSArCiAgc3RhdF9zbW9vdGgobWV0aG9kPSJsbSIsIGZvcm11bGEgPSB5IH4geCkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yPXNhbXBsZUlEKSkgKwogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXM9ImZyZWUiKSArICB0aGVtZV9idygpCmBgYApUaGUgY29uc2VxdWVuY2Ugb2YgcmVub3JtYWxpemluZyBmb3JjZXMgdGhlIGRyb3BvdXQgbWlkcG9pbnQgbG9jYXRpb25zIHRvIGJlIHVuaWZvcm1seSBvdmVyL3VuZGVyIGVzdGltYXRlZCBmb3IgY3VydmVzIGhhdmluZyB0cnVlIGxvd2VyL2hpZ2hlciBsb2NhdGlvbnMgKGFuZCB0aGUgc2NhbGluZyBmYWN0b3IgcmVtYWlucyBhcyBiZWZvcmUsIHVuaWZvcm1seSBvdmVyLWVzdGltYXRlZCkuIFRoZXNlIHNtYWxsIGRpc3RvcnRpb25zIGluIGRyb3BvdXQgY3VydmVzIGNvdWxkIGhhdmUga25vY2stb24gZWZmZWN0cyBmb3IgZXN0aW1hdGlvbiBvZiBjb2VmZmljaWVudHMgZnJvbSBncm91cHMgaGF2aW5nIG11bHRpcGxlIGRyb3BvdXQgc2FtcGxlczoKCmBgYHtyfQpub3Jtc2VzIDwtIG1hdHJpeCh1bmxpc3QobGFwcGx5KGxhcHBseShub3JtZml0JGNvZWZmaWNpZW50X3ZhcmlhbmNlX21hdHJpY2VzLCBkaWFnKSwgc3FydCkpLAogICAgICAgICAgICAgICAgICBuY29sPTMsIGJ5cm93PVQpCmNvbG5hbWVzKG5vcm1zZXMpIDwtIGNvbG5hbWVzKG5vcm1maXQkY29lZmZpY2llbnRfdmFyaWFuY2VfbWF0cmljZXNbWzFdXSkKbm9ybXNlcyA8LSBhcy5kYXRhLmZyYW1lKG5vcm1zZXMpCm5vcm1zZXMkcGVwdGlkZSA8LSByb3duYW1lcyhub3JtZml0JGNvZWZmaWNpZW50cykKbm9ybXNlcyRsb3dxdCA8LSBxdCgwLjAyNSwgbm9ybWZpdCRmZWF0dXJlX3BhcmFtZXRlcnMkZGYpCm5vcm1zZXMkaGlnaHF0IDwtIHF0KDAuOTc1LCBub3JtZml0JGZlYXR1cmVfcGFyYW1ldGVycyRkZikKbm9ybWNpcyA8LSBtZWx0KG5vcm1zZXMsCiAgICAgICAgICAgICAgICBpZD1jKCJwZXB0aWRlIiwgImhpZ2hxdCIsICJsb3dxdCIpLAogICAgICAgICAgICAgICAgdmFyaWFibGUubmFtZSA9ICJncm91cCIsCiAgICAgICAgICAgICAgICB2YWx1ZS5uYW1lID0gInNlcyIpCm5vcm1jaXMkbG93IDwtIG5vcm1jaXMkbG93cXQgKiBub3JtY2lzJHNlcwpub3JtY2lzJGhpZ2ggPC0gbm9ybWNpcyRoaWdocXQgKiBub3JtY2lzJHNlcwoKbm9ybWRmIDwtIG1lcmdlKGRyb3BkZiwgbWVyZ2UobWVsdChub3JtY29lZiwgdmFsdWUubmFtZSA9ICJlc3RpbWF0ZSIpLCBtZWx0KG11X2lqLCB2YWx1ZS5uYW1lID0gInZhbHVlIikpKQpub3JtZGYgPC0gbWVyZ2Uobm9ybWRmLCBub3JtY2lzKQoKbm9ybWRmJGVzdGxvdyA8LSBub3JtZGYkZXN0aW1hdGUgKyBub3JtZGYkbG93Cm5vcm1kZiRlc3RoaWdoIDwtIG5vcm1kZiRlc3RpbWF0ZSArIG5vcm1kZiRoaWdoCm5vcm1kZiRlc3RyYW5nZSA8LSBub3JtZGYkZXN0aGlnaCAtIG5vcm1kZiRlc3Rsb3cKCmdncGxvdChub3JtZGYsIGFlcyh4PXZhbHVlLCB5PWVzdGltYXRlLCBzaGFwZT1ncm91cCwgY29sb3I9ZmFjdG9yKGRyb3BudW0pKSkgKwogIGdlb21fcG9pbnQoKSArIGdlb21fYWJsaW5lKHNsb3BlPTEsIGludGVyY2VwdD0wKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKG5hbWU9IiMgZHJvcG91dHMiLCBwYWxldHRlPSJTZXQyIikgKwogIGZhY2V0X3dyYXAofmRyb3BudW0pICsgdGhlbWVfYncoKQpgYGAKIyBTYW1wbGUgUUMgdXNpbmcgZGlzdGFuY2VzCgpXZSBrbm93IHRoYXQgZGlmZmVyZW5jZXMgaW4gZHJvcG91dCBhYnVuZGFuY2VzIGNhbiBsZWFkIHRvIHNwdXJpb3VzIFBDQSBwcm9qZWN0aW9ucyBkdXJpbmcgc2FtcGxlLWxldmVsIFFDOyBgcHJvREFgIHByb3ZpZGVzIGEgcHJvYmFiaWxpc3RpYyBkaXN0YW5jZSBtZWFzdXJlbWVudCB0aGF0IGNvdWxkIGF2b2lkIHN1Y2guIExldCdzIGV4cGxvcmU6CgpgYGB7cn0KbWF0eSA8LSB5X2lqCm1hdHlbaXMubmEobWF0eSldIDwtIGxvZzIoMSkKbWF0eSA8LSBub3JtYWxpemVCZXR3ZWVuQXJyYXlzKG1hdHksIG1ldGhvZD0icXVhbnRpbGUiKQoKcGNhIDwtIHByY29tcChtYXR5LCBzY2FsZT1UKQpnZ3Bsb3QoYXMuZGF0YS5mcmFtZShwY2Ekcm90YXRpb24pLCBhZXMoeD1QQzEsIHk9UEMyLCBjb2xvcj1jb2xuYW1lcyhtYXR5KSkpICsKICBnZW9tX3BvaW50KCkgKyB0aGVtZV9idygpCgpkbSA8LSBkaXN0X2FwcHJveChmaXQpCmhlYXRtYXAoYXMubWF0cml4KGRtJG1lYW4pKQpgYGAKCldoaWxlIGVhY2ggc2FtcGxlIGhhcyBmZXdlci9tb3JlIGRyb3BvdXRzLCB0aGUgaGVhdG1hcCBwYWlyd2lzZSBzYW1wbGUgZGlzdGFuY2VzIGFwcGVhciB1bmlmb3JtIHdpdGhpbi9iZXR3ZWVuIGVhY2ggZXhwZXJpbWVudGFsIGdyb3VwLCBhcyBleHBlY3RlZC4=