For this hands-on exercise, we will use an adaptation of the pig_adg dataset described in Dohoo, Martin and Wayne - Veterinary Epidemiologic Research (second edition) (see (Dohoo, Martin, Stryhn, & others, 2003)).
Let’s install all required and suggest package from abn (if not already done)
## dplyr is used for the hands-on exercise
install.package(c("nnet", "lme4", "Rgraphviz", "knitr", "R.rsp", "testthat", "entropy", "moments", "boot", "brglm", "dplyr"))
Let’s load the data into the working environment:
require('abn'); data("adg", package = "abn")
let us work with a data.frame as required by abn. adg <- adg %>% as.data.frame()
The data for this exercise consist of 341 observations of 9 variables. Here is an extract of the first rows:
knitr::kable(head(adg)) %>%
kable_styling(bootstrap_options = c("striped", "hover"))
AR
|
pneumS
|
female
|
livdam
|
eggs
|
wormCount
|
age
|
adg
|
farm
|
1
|
0
|
0
|
1
|
0
|
0
|
196
|
453
|
4
|
1
|
0
|
0
|
1
|
0
|
0
|
175
|
501
|
9
|
0
|
0
|
1
|
1
|
0
|
0
|
176
|
545
|
3
|
1
|
0
|
1
|
1
|
0
|
0
|
207
|
441
|
8
|
1
|
1
|
0
|
0
|
0
|
0
|
222
|
482
|
13
|
1
|
0
|
0
|
0
|
0
|
0
|
212
|
423
|
8
|
The meaning of each variable is the following:
Variable
|
Meaning
|
Distribution
|
AR
|
presence of atrophic rhinitis
|
Binomial
|
pneumS
|
presence of moderate to severe pneumonia
|
Binomial
|
female
|
sex of the pig (1=female, 0=castrated)
|
Binomial
|
livdam
|
presence of liver damage (parasite-induced white spots)
|
Binomial
|
eggs
|
presence of fecal/gastrointestinal nematode eggs at time of slaughter
|
Binomial
|
wormCount
|
count of nematodes in small intestine at time of slaughter
|
Poisson
|
age
|
days elapsed from birth to slaughter (days)
|
Continuous
|
adg
|
average daily weight gain (grams)
|
Continuous
|
farm
|
farm ID
|
Discrete
|
The distribution of each variable is the following:
For this exercise we will drop farm (clustering indicator):
drop <- which(colnames(adg)%in% c("farm"))
abndata <- adg[, -drop]
Set all binary variables to factor data type (and check that dataset is complete)
A requirement of abn (Kratzer, Pittavino, Lewis, & Furrer, 2017; Lewis & Ward, 2013) is that all binary variables are coerced into factors.
str(abndata)
'data.frame': 341 obs. of 8 variables:
$ AR : num 1 1 0 1 1 1 1 1 1 1 ...
$ pneumS : num 0 0 0 0 1 0 0 0 0 0 ...
$ female : num 0 0 1 1 0 0 1 1 1 0 ...
$ livdam : num 1 1 1 1 0 0 0 1 1 0 ...
$ eggs : num 0 0 0 0 0 0 0 0 0 0 ...
$ wormCount: num 0 0 0 0 0 0 0 0 0 0 ...
$ age : num 196 175 176 207 222 212 247 181 191 181 ...
$ adg : num 453 501 545 441 482 423 407 580 427 586 ...
abndata[,1:5] <- as.data.frame(lapply(abndata[,1:5], factor))
sum(complete.cases(abndata))
[1] 341
Setup the distribution list for each variable
Each variable in the model needs to be associated to a distribution (currently available: binomial, gaussian, poisson) according to the type of data. In this example most of the variables are binary and therefore associated to the binomial distribution. Variables age and adg are continuous, so they will be associated to the Gaussian distribution. Finally, variable wormCount is a count and can be modelled by a Poisson distribution.
The supported distributions are:
- gaussian with identity link function
- binomial with logit link function (required data to be factor)
- Poisson distributed with log link function
- multinomial with logit link function (required data to be factor. Only available with mle fitting)
dist <- list(AR = "binomial", pneumS = "binomial", female="binomial",
livdam= "binomial", eggs = "binomial",wormCount = "poisson",
age= "gaussian", adg = "gaussian")
Create retain and banned matrixes (optional)
Prior knowledge about data structure, that could guide the search for the optimal model, can be included by forcing or banning some specific arcs from being considered in the final DAG. This is done by providing a retain matrix and/or a ban matrix. We will start by creating two empty matrices with the same size as our data (8) and named rows and columns.
retain <- matrix(0, ncol(abndata), ncol(abndata))
colnames(retain) <- rownames(retain) <- names(abndata)
banned <- matrix(0, ncol(abndata), ncol(abndata))
colnames(banned) <- rownames(banned) <- names(abndata)
Ban some arcs (optional)
The information encoded in the ban matrix is subjectively chosen to reflect our belief about data structure. In this example, it is reasonable to assume that none of the variables in the model is going to affect the gender of the animal (which is an inborn trait). To encode this information we will ban all the arcs going to female, by setting their value to 1 (banned) as opposite to the default value 0 (non banned).
How does it work?
Rows are children, columns parents:
. b1 b2 b3 b4
b1 0 1 0 0
b2 0 0 0 0
b3 1 0 0 0
b4 0 0 0 0
So ban[1, 2] <- 1
means do not allow the arc from b2 (second column) to b1 (first row) and ban[3, 1] <- 1
means do not allow the arc from b1 (first column) to b3 (third row).
Now, we want to ban the arcs going from any variable (= all columns except the third) to female (= third row):
banned[3,-3] <- 1
Have a look if you want:
banned
AR pneumS female livdam eggs wormCount age adg
AR 0 0 0 0 0 0 0 0
pneumS 0 0 0 0 0 0 0 0
female 1 1 0 1 1 1 1 1
livdam 0 0 0 0 0 0 0 0
eggs 0 0 0 0 0 0 0 0
wormCount 0 0 0 0 0 0 0 0
age 0 0 0 0 0 0 0 0
adg 0 0 0 0 0 0 0 0
Try to run one search
Start with 1 parent as maximum limit
abn supports an mle and Bayesian implementation. We will run a Bayesian (default) analysis.
To make the search more efficient, we constrain the maximum number of parents allowed for each node. We will start from 1 and increase subsequently until the network score does not improve further even when more parents are allowed.
max.par <- 1
Build a cache of all local computations
library(abn)
mycache <- buildScoreCache(data.df = as.data.frame(abndata), data.dists = dist,
dag.banned = banned, dag.retained = retain,
max.parents = max.par)
The function buildScoreCache() returns a named list of class abnCache that contains all necessary data and infromation to be used in mostProbable(). For each node, the buildScoreCache() function tries every combination of parent nodes (always satisfying the constraints max.par, banned, retained) and computes a score. The parent children combinations are then stored, together with their scores. This object contains all the information needed for different functions, that try to learn the structure of the network.
Run the exact search for a specified parent limit
- Find the optimal DAG given the parent limit
mydag <- mostProbable(score.cache = mycache)
Step1. completed max alpha_i(S) for all i and S
Total sets g(S) to be evaluated over: 256
- Fit marginal densities
fabn <- fitAbn(object = mydag)
- check network score
fabn$mlik
[1] -2806.499
The function mostProbable() returns an object of class abnMostprobable, which is a list containing: a matrix giving the DAG definition of the most probable posterior structure, the cache of pre-computed scores and the score used for selection.
The function fitAbn() returns an object of class abnFit. A named list. One entry for each of the variables in data.df (excluding the grouping variable, if present) which contains an estimate of the log marginal likelihood for that individual node. An entry “mlik” which is the total log marginal likelihood for the full ABN model. A vector of error.codes - non-zero if a numerical error or warning occurred, and a vector error.code.desc giving a text description of the error. A list modes, which contains all the mode estimates for each parameter at each node. A vector called Hessian accuracy, which is the estimated local error in the log marginal likelihood for each node. If compute.fixed=TRUE then a list entry called marginals which contains a named entry for every parameter in the ABN and each entry in this list is a two-column matrix where the first column is the value of the marginal parameter, say x, and the second column is the respective density value, pdf(x). Also, a list called marginal.quantiles is produced, giving the quantiles for each marginal posterior distribution. If create.graph=TRUE then an additional entry graph which is of type class graphAM-class (Rgraphviz) is created.
Run the exact search across incremental parent limits
Repeat step 5 for incremental parent limit (e.g. 1 to nr.var-1). The optimal DAG is the one where the network score does not improve (i.e. becomes bigger) any longer by allowing more parents.
datadir <- tempdir()
for (i in 1:7) {
max.par <- i
mycache <- buildScoreCache(data.df = as.data.frame(abndata), data.dists = dist,
dag.banned = banned, dag.retained = retain,
max.parents = max.par)
mydag <- mostProbable(score.cache = mycache)
fabn <- fitAbn(object = mydag)
cat(paste("network score for", i, "parents =", fabn$mlik, "\n\n"))
save(mycache, mydag, fabn, file = paste(datadir,"mp_", max.par,".RData", sep=""))
}
Step1. completed max alpha_i(S) for all i and S
Total sets g(S) to be evaluated over: 256
network score for 1 parents = -2806.49909464438
Step1. completed max alpha_i(S) for all i and S
Total sets g(S) to be evaluated over: 256
network score for 2 parents = -2714.69171704676
Step1. completed max alpha_i(S) for all i and S
Total sets g(S) to be evaluated over: 256
network score for 3 parents = -2709.37078842511
Step1. completed max alpha_i(S) for all i and S
Total sets g(S) to be evaluated over: 256
network score for 4 parents = -2709.25275104164
Step1. completed max alpha_i(S) for all i and S
Total sets g(S) to be evaluated over: 256
network score for 5 parents = -2709.25275104164
Step1. completed max alpha_i(S) for all i and S
Total sets g(S) to be evaluated over: 256
network score for 6 parents = -2709.25275104164
Step1. completed max alpha_i(S) for all i and S
Total sets g(S) to be evaluated over: 256
network score for 7 parents = -2709.25275104164
# get network score for all parent limits
# ---------------------------------------
mp.mlik <- c()
for (i in 1:max.par) {
load(paste(datadir,"mp_", i,".RData", sep=""))
mp.mlik <- c(mp.mlik, fabn$mlik)
}
Plot log marginal likelihood in function of the parent limit
# check how it looks
# ------------------
plot(1:max.par, mp.mlik, xlab = "Parent limit", ylab = "Log marginal likelihood",
type = "b", col="red", ylim=range(mp.mlik))
abline(v=which(mp.mlik==max(mp.mlik))[1], col="grey", lty=2)
After max.par=4 the maximum log marginal likelihood is constant:
[1] -2806.499 -2714.692 -2709.371 -2709.253 -2709.253 -2709.253 -2709.253
Here is what the best fitting DAG looks like using plotAbn() function:
Before going ahead interpreting the results, we need one more step. So far, we have identified a DAG which has the maximum possible goodness of fit according to the log marginal likelihood. This is the standard goodness of fit metric in Bayesian modelling and includes an implicit penalty for model complexity. However, the log marginal likelihood is also known to be prone to overfitting (especially with smaller data sets), meaning that it may identify more parameters than can be actually justified by the data. Therefore, it is advisable to check and address potential overfitting before drawing any conclusion based on the model results.
A well established approach for addressing overfitting is to use parametric bootstrapping. Basically, the model chosen from the exact search will be used to generate many bootstrap datasets (e.g. 1000) of equal size to the original dataset (n=341 in our case). Each bootstrap dataset will be then treated as if it were the original data, and a globally optimal DAG will be identified exactly as described before (i.e. exact search). We will therefore get as many DAGs as the number of simulated datasets (eg. 1000). To address overfitting, any arcs in the DAG from the original data which will not be recovered in > 50% of the bootstrap DAGs will be deemed to have insufficient statistical support to be considered robust.
Parametric bootstrapping
This step will be done with the aid of a software for Bayesian statistical analysis using Markov Chain Monte Carlo (MCMC) simulations (we will use JAGS (Plummer & others, 2003), but any other is fine). We will use the parameters estimated from our model to build a BUG model to simulate data. In other words, we will do the reverse process: instead of using data to estimate parameters, we will use parameters to estimate data.
Extract parameters from best fitting model and save them for MCMC simulations
The parameters at each node are estimated as posterior probability density distributions. These marginal densities are the ones where JAGS needs to sample from in order to simulate data. In order to use these distributions with JAGS, the densities need to be approximated by a discrete distribution over a fine and equally spaced grid. Here is a mock example for a hypothetical parameter \(\beta\):
# Fit marginal densities over a fixed grid --> n.grid=1000
# --------------------------------------------------------
marg.f <- fitAbn(object = mydag, compute.fixed=TRUE, n.grid=1000)
# Extract values
# --------------
m <- marg.f$marginals[[1]]
for(i in 2: length(marg.f$marginals))
{ m <- c(m, marg.f$marginals[[i]])}
# Bind all the marginals for the same node into a matrix
# ------------------------------------------------------
AR.p <- cbind( m[[ "AR|(Intercept)"]], m[[ "AR|age"]])
pneumS.p <- cbind( m[[ "pneumS|(Intercept)"]], m[[ "pneumS|age"]])
female.p <- cbind( m[[ "female|(Intercept)"]])
livdam.p <- cbind( m[[ "livdam|(Intercept)"]], m[[ "livdam|eggs"]])
eggs.p <- cbind( m[[ "eggs|(Intercept)"]], m[[ "eggs|adg"]])
wormCount.p <- cbind( m[[ "wormCount|(Intercept)"]], m[[ "wormCount|AR"]],
m[[ "wormCount|eggs"]], m[[ "wormCount|age"]], m[[ "wormCount|adg"]])
age.p <- cbind( m[[ "age|(Intercept)"]], m[[ "age|female"]])
prec.age.p <- cbind( m[[ "age|precision" ]])
adg.p <- cbind( m[[ "adg|(Intercept)"]], m[[ "adg|age"]])
prec.adg.p <- cbind( m[[ "adg|precision" ]])
# Save it to a file named PostParams to be read by JAGS
# -----------------------------------------------------
dump(c("AR.p", "pneumS.p", "female.p", "livdam.p", "eggs.p",
"wormCount.p", "age.p", "prec.age.p", "adg.p", "prec.adg.p"),
file="PostParams.R")
Write the BUG model
In order to simulate the variables of our dataset we need to provide a model for each of them, using the aforementioned parameters estimates. For instance, the binomial node AR in our DAG has one incoming arc coming from the node age. In a regression notation this would be translated into:
logit(AR) = \(\alpha\) + \(\beta\) x age + \(\epsilon\)
where \(\alpha\) is the intercept and \(\beta\) the regression coefficient for variable age and \(\epsilon\) is the error term modelled by a binomial distribution.
Given that we will simulate the data in a Bayesian framework, AR will be modelled as a probability distribution. Therefore it will look like:
AR ~ dbern(probAR);
logit(probAR)<- alpha + beta*age;
Then, the values of alpha and beta will be sampled (dcat) from our discrete distribution of parameters:
alpha.prob ~ dcat(AR.p[ ,2]);
–> sample from the vector of density values f(x) (second column in matrix)
alpha ~ AR.p[alpha.prob,1];
–> corresponding x value for the sampled density (first column in matrix)
beta.prob ~ dcat(AR.p[ ,4]);
beta ~ AR.p[beta.prob,1];
The BUG file (model8vPois.bug) can be retrieved from the file directory.
Run JAGS and inspect the result of a simulated dataset
library(rjags)
# set inits
# ---------
init <- list(".RNG.name"="base::Mersenne-Twister", ".RNG.seed"=42)
# load data
# ---------
source("PostParams.R")
# run model once
# --------------
jj <- jags.model(file = "model8vPois.bug",
data = list( 'AR.p'=AR.p , 'pneumS.p'=pneumS.p , 'female.p'=female.p,
'livdam.p'=livdam.p , 'eggs.p'=eggs.p , 'wormCount.p'=wormCount.p ,
'age.p'=age.p ,'prec.age.p'=prec.age.p,
'adg.p'=adg.p , 'prec.adg.p'=prec.adg.p),
inits = init,
n.chains = 1,
n.adapt = 500)
Compiling model graph
Resolving undeclared variables
Allocating nodes
Graph information:
Observed stochastic nodes: 0
Unobserved stochastic nodes: 28
Total graph size: 4091
Initializing model
# run more iterations
# -------------------
update(jj, 100000)
# set number of observation we want to extract for a dataset
# ----------------------------------------------------------
n.obs=341
# sample data (same size as original: 341) with a sampling lag (20) to reduce avoid autocorrelation
# -------------------------------------------------------------------------------------------------
samp <- coda.samples(jj, c("AR", "pneumS", "female", "livdam", "eggs",
"wormCount", "age", "prec.age", "adg", "prec.adg"),
n.iter= n.obs*20 , thin =20)
Now compare the simulated data with the original data. Observe that Gaussian nodes are by default centred when doing abn search, meaning that the simulated data for those nodes will be centred as well.
Simulated data looks fairly OK (perhaps wormCount is sub-optimal as the simulated data miss to represent the long right tail) so we can proceed with the bootstrapping.
Iterate dataset simulation + exact search over and over
What we will do is to create a loop to 1) simulate data, 2) do exact search on such data, and 3) store the best fitting DAG over and over for many times (e.g. 1000 iterations). Bootstrap data need to be saved in a folder to be further inspected.
# load data
data("adg", package = "abn")
df <- adg %>%
dplyr::select(-farm) %>%
as.data.frame()
vars <- colnames(df)
# load data for jags
source("PostParams.R")
# select nr. bootstrap samples to run
# -----------------------------------
set.seed(46846)
n <- sample(1:100000, 1000)
# specify max number of parents based on previous search
# ------------------------------------------------------
max.par <- 4
# Simulate data and run ABN on such dataset
# -----------------------------------------
boot.save <- list()
for (i in 1:length(n)) {
print(paste("Running simulation", i))
# pick initials
init <- list(".RNG.name"="base::Mersenne-Twister", ".RNG.seed"=n[i])
# run model
jj <- jags.model(file = "model8vPois.bug",
data = list( 'AR.p'=AR.p , 'pneumS.p'=pneumS.p , 'female.p'=female.p,
'livdam.p'=livdam.p , 'eggs.p'=eggs.p , 'wormCount.p'=wormCount.p ,
'age.p'=age.p ,'prec.age.p'=prec.age.p,
'adg.p'=adg.p , 'prec.adg.p'=prec.adg.p),
inits = init,
n.chains = 1,
n.adapt = 500)
# run more iterations
update(jj, 100000)
# sample data (same size as original: 341) with a sampling lag (20) to reduce avoid autocorrelation
samp <- coda.samples(jj, c("AR", "pneumS", "female", "livdam", "eggs",
"wormCount", "age", "prec.age", "adg", "prec.adg"),
n.iter= 6820 , thin =20)
# build dataframe in the same shape as the original one
dt.boot <- as.data.frame(as.matrix(samp)) # pay attention at order names
dt.boot<- dt.boot[, vars]
# now coerce to factors if need be and set levels - NOTES setting levels works as
# "0" "1" is in the same order as "absent" "present" from original data
#abndata <- as.data.frame(abndata)
dt.boot[,1:5] <- as.data.frame(lapply(dt.boot[,1:5], factor))
# Build a cache of all local computations
# ---------------------------------------
mycache <- buildScoreCache(data.df = dt.boot, data.dists = dist, dag.banned = banned,
dag.retained = retain, max.parents = max.par)
# Run the EXACT SEARCH
# --------------------
mp.dag <- mostProbable(score.cache = mycache)
fabn <- fitAbn(object = mp.dag)
# Save the results obtained
# -------------------------
#boot.save[[i]] <- list(mp.dag, fabn, dt.boot)
boot.save[[i]] <- mp.dag$dag
}
save(boot.save, file = sprintf('BootData/dt.boot.RData'))
Find the final pruned DAG
First we need to load all the bootstrap DAGs.
# set nr bootstrap samples
# ------------------------
n <- 1000
# load dags and boostrap data
# ------------------------------
dags <- list()
boot <- list()
load(file = 'BootData/dt.boot.RData')
for(i in 1:n){
dags[[i]] <- boot.save[[i]]
}
Then we will check what was the most frequent number of arcs:
# count total number of arcs in each dag
# --------------------------------------
arcs <- sapply(dags, sum)
barplot(table(arcs))
For comparison, in the original DAG there were 10 arcs, so it seems that there might have been some overfitting.
Finally, we will count how many times each arc appears in the bootstrapped data. The final pruned DAG will include only arcs present in at least 50% of bootstrap samples (e.g. 500/1000 in our case).
# Count how many times each arc appear in the bootstrap data
# ----------------------------------------------------------
# function Reduce sums ("+") each element of each matrix in the list and store
# results in a new matrix of same size
alldag <- Reduce("+", dags)
# express it in percentage
perdag <- alldag/length(dags)
# Keep only arcs that appears in at least 50% of samples
# ------------------------------------------------------
trim.dag <- (alldag >=(n*0.5))*1
# Send final pruned DAG to Graphvis for visualization
# -----------------------------------------------------
toGraphviz(dag = trim.dag, data.df = abndata, data.dists = dist,
outfile = paste0("TrimDAG",n,".dot"))
This is the percentage of arcs retrieved within the bootstrap sample:
AR pneumS female livdam eggs wormCount age adg
AR 0 0 0 0 0 0 76 5
pneumS 1 0 1 0 0 0 39 8
female 0 0 0 0 0 0 0 0
livdam 0 0 0 0 50 1 0 0
eggs 0 0 1 27 0 0 9 72
wormCount 69 0 0 0 100 0 100 61
age 19 17 57 0 4 0 0 28
adg 1 2 12 0 7 0 72 0
This is how the final pruned DAG looks like:
Now that we have a robust model (encoded by the pruned DAG), we can extract the parameters to (a) appreciate the magnitude (and precision) of the the associations and (b) further refine the DAG including whether the associations are positive or negative.
Get the table of quantiles for the marginals
# extract marginals adjusted for grouped data
marg.dists <- marg.f$marginals[[1]]
for (i in 2:length(marg.f$marginals)) {
marg.dists <- c(marg.dists, marg.f$marginals[[i]])
}
mat <- matrix(rep(NA, length(marg.dists)*3), ncol=3)
rownames(mat) <- names(marg.dists)
colnames(mat) <- c("2.5%", "50%", "97.5%")
ignore.me <- union(grep("\\(Int", names(marg.dists)), grep("prec", names(marg.dists))) # take away background k and precision
comment <- rep("", length(marg.dists))
for (i in 1:length(marg.dists)) {
tmp <- marg.dists[[i]]
tmp2 <- cumsum(tmp[,2])/sum(tmp[,2])
mat[i, ] <-c(tmp[which(tmp2>0.025)[1],1],## -1 is so use value on the left of the 2.5%
tmp[which(tmp2>0.5)[1],1],
tmp[which(tmp2>0.975)[1],1])
vec <- mat[i,]
if( !(i%in%ignore.me) && (vec[1]<0 && vec[3]>0)){comment[i]<-"not sig. at 5%"}
## truncate for printing
mat[i,]<-as.numeric(formatC(mat[i,],digits=3,format="f"))
}
knitr::kable(cbind(mat), row.names = TRUE, digits = 3, align = "rrrr", "html") %>%
kable_styling(bootstrap_options = "striped", full_width = F, position = "left") %>%
column_spec(3, bold = TRUE)
|
2.5%
|
50%
|
97.5%
|
AR|(Intercept)
|
0.890
|
1.158
|
1.438
|
AR|age
|
0.504
|
0.797
|
1.104
|
pneumS|(Intercept)
|
-2.129
|
-1.809
|
-1.515
|
female|(Intercept)
|
-0.196
|
0.022
|
0.231
|
livdam|(Intercept)
|
0.980
|
1.260
|
1.565
|
livdam|eggs
|
0.662
|
1.554
|
2.649
|
eggs|(Intercept)
|
-1.504
|
-1.229
|
-0.967
|
eggs|adg
|
0.353
|
0.609
|
0.889
|
wormCount|(Intercept)
|
-1.642
|
-1.397
|
-1.172
|
wormCount|AR
|
0.154
|
0.272
|
0.396
|
wormCount|eggs
|
3.284
|
3.507
|
3.741
|
wormCount|age
|
-0.823
|
-0.684
|
-0.545
|
wormCount|adg
|
-0.371
|
-0.243
|
-0.109
|
age|(Intercept)
|
-0.346
|
-0.197
|
-0.055
|
age|female
|
0.190
|
0.393
|
0.605
|
age|precision
|
0.896
|
1.040
|
1.204
|
adg|(Intercept)
|
-0.052
|
0.001
|
0.052
|
adg|age
|
-0.920
|
-0.867
|
-0.816
|
adg|precision
|
3.500
|
4.068
|
4.711
|
As said, the marginals represent estimates of the parameters at each node (i.e. the arcs in the DAG). Being the variables of different nature, they have different meaning. Marginals represent correlation coefficients for Gaussian nodes (when the default centering of variables is kept), log rate ratios for Poisson nodes, and log odds ratios for binomial nodes. Therefore, the second and the latter need to be exponentiated, to get the odds ratios or rate ratios respectively.
|
2.5Q
|
median
|
97.5Q
|
Interpretation
|
AR|age
|
1.655
|
2.219
|
3.016
|
odds ratio
|
livdam|eggs
|
1.939
|
4.730
|
14.140
|
odds ratio
|
eggs|adg
|
1.423
|
1.839
|
2.433
|
odds ratio
|
wormCount|AR
|
0.154
|
0.272
|
0.396
|
rate ratio
|
wormCount|eggs
|
3.284
|
3.507
|
3.741
|
rate ratio
|
wormCount|age
|
-0.823
|
-0.684
|
-0.545
|
rate ratio
|
wormCount|adg
|
-0.371
|
-0.243
|
-0.109
|
rate ratio
|
age|female
|
0.190
|
0.393
|
0.605
|
correlation
|
adg|age
|
-0.920
|
-0.867
|
-0.816
|
correlation
|
Present final results
As a final step we will refine the DAG by (a) changing the style of the arrows according to the type of association (i.e. solid arrows for positive association and dashed arrows for negative association), and (b) changing the thickness of the arrows according to their link strength. These steps are done in Graphviz, using the DOT language. Details can be found at https://www.graphviz.org/doc/info/attrs.html.
|
2.5Q
|
median
|
97.5Q
|
Interpretation
|
AR|age
|
1.655
|
2.219
|
3.016
|
odds ratio
|
livdam|eggs
|
1.939
|
4.730
|
14.140
|
odds ratio
|
eggs|adg
|
1.423
|
1.839
|
2.433
|
odds ratio
|
wormCount|AR
|
0.154
|
0.272
|
0.396
|
rate ratio
|
wormCount|eggs
|
3.284
|
3.507
|
3.741
|
rate ratio
|
wormCount|age
|
-0.823
|
-0.684
|
-0.545
|
rate ratio
|
wormCount|adg
|
-0.371
|
-0.243
|
-0.109
|
rate ratio
|
age|female
|
0.190
|
0.393
|
0.605
|
correlation
|
adg|age
|
-0.920
|
-0.867
|
-0.816
|
correlation
|
For an example in a recent peer reviewed publication see (Comin, Jeremiasson, Kratzer, & Keeling, 2019) or (Kratzer et al., 2020)
References
Comin, A., Jeremiasson, A., Kratzer, G., & Keeling, L. (2019). Revealing the structure of the associations between housing system, facilities, management and welfare of commercial laying hens using additive bayesian networks. Preventive Veterinary Medicine, 164, 23–32.
Dohoo, I. R., Martin, W., Stryhn, H., & others. (2003). Veterinary epidemiologic research. AVC Incorporated Charlottetown, Canada.
Kratzer, G., Lewis, F. I., Willi, B., Meli, M. L., Boretti, F. S., Hofmann-Lehmann, R., … Hartnack, S. (2020). Bayesian network modeling applied to feline calicivirus infection among cats in Switzerland. Frontiers in Veterinary Science, 7, 73.
Kratzer, G., Pittavino, M., Lewis, F., & Furrer, R. (2017). Abn: An r package for modelling multivariate data using additive bayesian networks. R Package Vignette.
Lewis, F. I., & Ward, M. P. (2013). Improving epidemiologic data analyses through multivariate regression modelling. Emerging Themes in Epidemiology, 10(1), 4.
Plummer, M., & others. (2003). JAGS: A program for analysis of bayesian graphical models using gibbs sampling. In Proceedings of the 3rd international workshop on distributed statistical computing (Vol. 124). Vienna, Austria.
LS0tCnRpdGxlOiAiSGFuZHMtb24gZXhlcmNpc2UgLSB1c2VSISBDb25mZXJlbmNlIDIwMjEiCmZvbnRzaXplOiAxNHB0Cm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCmJpYmxpb2dyYXBoeTogYmliLmJpYgpjc2w6IGFwYS5jc2wKCi0tLQoKJm5ic3A7CgpGb3IgdGhpcyBoYW5kcy1vbiBleGVyY2lzZSwgd2Ugd2lsbCB1c2UgYW4gYWRhcHRhdGlvbiBvZiB0aGUgKipwaWdfYWRnKiogZGF0YXNldCBkZXNjcmliZWQgaW4gKkRvaG9vLCBNYXJ0aW4gYW5kIFdheW5lIC0gVmV0ZXJpbmFyeSBFcGlkZW1pb2xvZ2ljIFJlc2VhcmNoIChzZWNvbmQgZWRpdGlvbikqIChzZWUgW0Bkb2hvbzIwMDN2ZXRlcmluYXJ5XSkuCgo8IS0tIGF2YWlsYmxlIGF0OiBodHRwOi8vcHJvamVjdHMudXBlaS5jYS92ZXIvZGF0YS1hbmQtc2FtcGxlcy8gLS0+CgombmJzcDsKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IEZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjb2xsYXBzZT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9NSwgY29tbWVudCA9IE5BKQoKCgpvcHRpb25zKHNjaXBlbj05OTkpCmxpYnJhcnkoYWJuKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoZHBseXIpCmBgYAoKTGV0J3MgaW5zdGFsbCBhbGwgcmVxdWlyZWQgYW5kIHN1Z2dlc3QgcGFja2FnZSBmcm9tICoqYWJuKiogKGlmIG5vdCBhbHJlYWR5IGRvbmUpCgpgYGB7ciBpbnN0YWxscGFja2FnZXMsIGVjaG89VFJVRSwgZXZhbD1GQUxTRX0KIyMgZHBseXIgaXMgdXNlZCBmb3IgdGhlIGhhbmRzLW9uIGV4ZXJjaXNlCgppbnN0YWxsLnBhY2thZ2UoYygibm5ldCIsICJsbWU0IiwgIlJncmFwaHZpeiIsICJrbml0ciIsICJSLnJzcCIsICJ0ZXN0dGhhdCIsICJlbnRyb3B5IiwgIm1vbWVudHMiLCAiYm9vdCIsICJicmdsbSIsICJkcGx5ciIpKQoKYGBgCgpMZXQncyBsb2FkIHRoZSBkYXRhIGludG8gdGhlIHdvcmtpbmcgZW52aXJvbm1lbnQ6ICAKCmByZXF1aXJlKCdhYm4nKTsgZGF0YSgiYWRnIiwgcGFja2FnZSA9ICJhYm4iKWAKCmxldCB1cyB3b3JrIHdpdGggYSBkYXRhLmZyYW1lIGFzIHJlcXVpcmVkIGJ5IGFibi4KYGFkZyA8LSBhZGcgJT4lIGFzLmRhdGEuZnJhbWUoKWAKCgombmJzcDsKClRoZSBkYXRhIGZvciB0aGlzIGV4ZXJjaXNlIGNvbnNpc3Qgb2YgKipgciBucm93KGFkZylgKiogb2JzZXJ2YXRpb25zIG9mICoqYHIgbmNvbChhZGcpYCoqIHZhcmlhYmxlcy4gSGVyZSBpcyBhbiBleHRyYWN0IG9mIHRoZSBmaXJzdCByb3dzOiAgCgoKCmBgYHtyIGRhdGFvdmVydmlldywgZWNobz1UUlVFLCBjYWNoZT1GQUxTRX0Ka25pdHI6OmthYmxlKGhlYWQoYWRnKSkgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiKSkKCmBgYAombmJzcDsKClRoZSBtZWFuaW5nIG9mIGVhY2ggdmFyaWFibGUgaXMgdGhlIGZvbGxvd2luZzogCgpgYGB7ciBkYXRhTWVhbmluZywgZWNobz1GQUxTRSwgY2FjaGU9RkFMU0V9Cm1tIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBjb2xuYW1lcyhhZGcpLAogICAgICAgICAgICAgICAgIE1lYW5pbmcgPSBjKCJwcmVzZW5jZSBvZiBhdHJvcGhpYyByaGluaXRpcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInByZXNlbmNlIG9mIG1vZGVyYXRlIHRvIHNldmVyZSBwbmV1bW9uaWEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzZXggb2YgdGhlIHBpZyAoMT1mZW1hbGUsIDA9Y2FzdHJhdGVkKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInByZXNlbmNlIG9mIGxpdmVyIGRhbWFnZSAocGFyYXNpdGUtaW5kdWNlZCB3aGl0ZSBzcG90cykiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicHJlc2VuY2Ugb2YgZmVjYWwvZ2FzdHJvaW50ZXN0aW5hbCBuZW1hdG9kZSBlZ2dzIGF0IHRpbWUgb2Ygc2xhdWdodGVyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY291bnQgb2YgbmVtYXRvZGVzIGluIHNtYWxsIGludGVzdGluZSBhdCB0aW1lIG9mIHNsYXVnaHRlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImRheXMgZWxhcHNlZCBmcm9tIGJpcnRoIHRvIHNsYXVnaHRlciAoZGF5cykiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJhdmVyYWdlIGRhaWx5IHdlaWdodCBnYWluIChncmFtcykiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJmYXJtIElEIiksIERpc3RyaWJ1dGlvbiA9IGMoIkJpbm9taWFsIiwgIkJpbm9taWFsIiwiQmlub21pYWwiLCJCaW5vbWlhbCIsIkJpbm9taWFsIiwgIlBvaXNzb24iLCAiQ29udGludW91cyIsICJDb250aW51b3VzIiwgIkRpc2NyZXRlIikpCgprbml0cjo6a2FibGUobW0sIHJvdy5uYW1lcyA9IEZBTFNFLCBkaWdpdHMgPSAyLCBhbGlnbiA9ICJsbCIsICJodG1sIikgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiKSxmdWxsX3dpZHRoID0gRkFMU0UsIHBvc2l0aW9uID0gImxlZnQiKQoKYGBgCgpUaGUgZGlzdHJpYnV0aW9uIG9mIGVhY2ggdmFyaWFibGUgaXMgdGhlIGZvbGxvd2luZzogCgpgYGB7ciBkYXRhRGlzdCwgZWNobz1GQUxTRSwgY2FjaGU9RkFMU0UsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0KCnBhcihtZnJvdz1jKDMsMyksIG1hcj1jKDIsNCwxLjUsMSkpCnh4IDwtIGJhcnBsb3QodGFibGUoYWRnJEFSKS8zNDEsIHlsaW09YygwLDEpLCBtYWluPSJBUiIsIHlsYWI9InByb3BvcnRpb24iKQp0ZXh0KHggPSB4eCwgeSA9IHRhYmxlKGFkZyRBUikvMzQxLCBsYWJlbCA9IHRhYmxlKGFkZyRBUiksIHBvcyA9IDMsIGNleCA9IDAuOCwgY29sID0gInJlZCIpCnh4IDwtIGJhcnBsb3QodGFibGUoYWRnJHBuZXVtUykvMzQxLCB5bGltPWMoMCwxKSwgbWFpbj0icG5ldW1TIiwgeWxhYj0icHJvcG9ydGlvbiIpCnRleHQoeCA9IHh4LCB5ID0gdGFibGUoYWRnJHBuZXVtUykvMzQxLCBsYWJlbCA9IHRhYmxlKGFkZyRwbmV1bVMpLCBwb3MgPSAzLCBjZXggPSAwLjgsIGNvbCA9ICJyZWQiKQp4eCA8LSBiYXJwbG90KHRhYmxlKGFkZyRmZW1hbGUpLzM0MSwgeWxpbT1jKDAsMSksIG1haW49ImZlbWFsZSIsIHlsYWI9InByb3BvcnRpb24iKQp0ZXh0KHggPSB4eCwgeSA9IHRhYmxlKGFkZyRmZW1hbGUpLzM0MSwgbGFiZWwgPSB0YWJsZShhZGckZmVtYWxlKSwgcG9zID0gMywgY2V4ID0gMC44LCBjb2wgPSAicmVkIikKeHggPC0gYmFycGxvdCh0YWJsZShhZGckbGl2ZGFtKS8zNDEsIHlsaW09YygwLDEpLCBtYWluPSJsaXZkYW0iLCB5bGFiPSJwcm9wb3J0aW9uIikKdGV4dCh4ID0geHgsIHkgPSB0YWJsZShhZGckbGl2ZGFtKS8zNDEsIGxhYmVsID0gdGFibGUoYWRnJGxpdmRhbSksIHBvcyA9IDMsIGNleCA9IDAuOCwgY29sID0gInJlZCIpCnh4IDwtIGJhcnBsb3QodGFibGUoYWRnJGVnZ3MpLzM0MSwgeWxpbT1jKDAsMSksIG1haW49ImVnZ3MiLCB5bGFiPSJwcm9wb3J0aW9uIikKdGV4dCh4ID0geHgsIHkgPSB0YWJsZShhZGckZWdncykvMzQxLCBsYWJlbCA9IHRhYmxlKGFkZyRlZ2dzKSwgcG9zID0gMywgY2V4ID0gMC44LCBjb2wgPSAicmVkIikKaGlzdChhZGckd29ybUNvdW50LCBtYWluPSJ3b3JtcyIscHJvYj1UUlVFLGNvbD0iZ3JleSIsYm9yZGVyPSJ3aGl0ZSIsIHlsaW09IGMoMCwwLjYpKQpsaW5lcyhkZW5zaXR5KGFkZyR3b3JtQ291bnQpLGx3ZD0xLjUpCmhpc3QoYWRnJGFnZSwgeGxhYj0iIiwgbWFpbj0iYWdlIixwcm9iPVRSVUUsY29sPSJncmV5Iixib3JkZXI9IndoaXRlIikKICBsaW5lcyhkZW5zaXR5KGFkZyRhZ2UpLGx3ZD0xLjUpCmhpc3QoYWRnJGFkZywgeGxhYj0iIiwgbWFpbj0iYWRnIixwcm9iPVRSVUUsY29sPSJncmV5Iixib3JkZXI9IndoaXRlIikKICBsaW5lcyhkZW5zaXR5KGFkZyRhZGcpLGx3ZD0xLjUpCmJhcnBsb3QodGFibGUoYWRnJGZhcm0pLCBtYWluPSJGYXJtIElEIiwgeWxpbT1jKDAsNDApLCB5bGFiPSJjb3VudCIsIGNvbC5tYWluID0gImdyYXk1MCIpCgoKYGBgCgombmJzcDsKCkZvciB0aGlzIGV4ZXJjaXNlIHdlIHdpbGwgZHJvcCAqZmFybSogKGNsdXN0ZXJpbmcgaW5kaWNhdG9yKTogIAoKCmBgYHtyIGRhdGFmaWx0ZXIsIGVjaG89VFJVRX0KCmRyb3AgPC0gd2hpY2goY29sbmFtZXMoYWRnKSVpbiUgYygiZmFybSIpKQphYm5kYXRhIDwtIGFkZ1ssIC1kcm9wXQoKYGBgCgombmJzcDsKCiMgU2V0IGFsbCBiaW5hcnkgdmFyaWFibGVzIHRvIGZhY3RvciBkYXRhIHR5cGUgKGFuZCBjaGVjayB0aGF0IGRhdGFzZXQgaXMgY29tcGxldGUpIAoKQSByZXF1aXJlbWVudCBvZiAqYWJuKiBbQGxld2lzMjAxM2ltcHJvdmluZzsgQGtyYXR6ZXIyMDE3YWJuXSBpcyB0aGF0IGFsbCBiaW5hcnkgdmFyaWFibGVzIGFyZSBjb2VyY2VkIGludG8gZmFjdG9ycy4gIAoKYGBge3IgZGF0YUZhY3RvciwgZWNobz1UUlVFfQoKc3RyKGFibmRhdGEpCmFibmRhdGFbLDE6NV0gPC0gYXMuZGF0YS5mcmFtZShsYXBwbHkoYWJuZGF0YVssMTo1XSwgZmFjdG9yKSkKc3VtKGNvbXBsZXRlLmNhc2VzKGFibmRhdGEpKQoKYGBgCgombmJzcDsKCiMgU2V0dXAgdGhlIGRpc3RyaWJ1dGlvbiBsaXN0IGZvciBlYWNoIHZhcmlhYmxlICAKCkVhY2ggdmFyaWFibGUgaW4gdGhlIG1vZGVsIG5lZWRzIHRvIGJlIGFzc29jaWF0ZWQgdG8gYSBkaXN0cmlidXRpb24gKGN1cnJlbnRseSBhdmFpbGFibGU6IGJpbm9taWFsLCBnYXVzc2lhbiwgcG9pc3NvbikgYWNjb3JkaW5nIHRvIHRoZSB0eXBlIG9mIGRhdGEuIEluIHRoaXMgZXhhbXBsZSBtb3N0IG9mIHRoZSB2YXJpYWJsZXMgYXJlIGJpbmFyeSBhbmQgdGhlcmVmb3JlIGFzc29jaWF0ZWQgdG8gdGhlICoqYmlub21pYWwqKiBkaXN0cmlidXRpb24uIFZhcmlhYmxlcyAqYWdlKiBhbmQgKmFkZyogYXJlIGNvbnRpbnVvdXMsIHNvIHRoZXkgd2lsbCBiZSBhc3NvY2lhdGVkIHRvIHRoZSAqKkdhdXNzaWFuKiogZGlzdHJpYnV0aW9uLiBGaW5hbGx5LCB2YXJpYWJsZSAqKndvcm1Db3VudCoqIGlzIGEgY291bnQgYW5kIGNhbiBiZSBtb2RlbGxlZCBieSBhICoqUG9pc3NvbioqIGRpc3RyaWJ1dGlvbi4gCgpUaGUgc3VwcG9ydGVkIGRpc3RyaWJ1dGlvbnMgYXJlOgoKKiBnYXVzc2lhbiB3aXRoIGlkZW50aXR5IGxpbmsgZnVuY3Rpb24KKiBiaW5vbWlhbCB3aXRoIGxvZ2l0IGxpbmsgZnVuY3Rpb24gKHJlcXVpcmVkIGRhdGEgdG8gYmUgZmFjdG9yKQoqIFBvaXNzb24gZGlzdHJpYnV0ZWQgd2l0aCBsb2cgbGluayBmdW5jdGlvbgoqIG11bHRpbm9taWFsIHdpdGggbG9naXQgbGluayBmdW5jdGlvbiAocmVxdWlyZWQgZGF0YSB0byBiZSBmYWN0b3IuIE9ubHkgYXZhaWxhYmxlIHdpdGggbWxlIGZpdHRpbmcpCgoKYGBge3IgZGlzdHMsIGVjaG89VFJVRX0KCmRpc3QgPC0gbGlzdChBUiA9ICJiaW5vbWlhbCIsIHBuZXVtUyA9ICJiaW5vbWlhbCIsIGZlbWFsZT0iYmlub21pYWwiLCAKICAgICAgICAgICAgIGxpdmRhbT0gImJpbm9taWFsIiwgZWdncyA9ICJiaW5vbWlhbCIsd29ybUNvdW50ID0gInBvaXNzb24iLAogICAgICAgICAgICAgYWdlPSAiZ2F1c3NpYW4iLCBhZGcgPSAiZ2F1c3NpYW4iKQoKYGBgCgombmJzcDsKCiMgQ3JlYXRlIHJldGFpbiBhbmQgYmFubmVkIG1hdHJpeGVzIChvcHRpb25hbCkgIAoKUHJpb3Iga25vd2xlZGdlIGFib3V0IGRhdGEgc3RydWN0dXJlLCB0aGF0IGNvdWxkIGd1aWRlIHRoZSBzZWFyY2ggZm9yIHRoZSBvcHRpbWFsIG1vZGVsLCBjYW4gYmUgaW5jbHVkZWQgYnkgZm9yY2luZyBvciBiYW5uaW5nIHNvbWUgc3BlY2lmaWMgYXJjcyBmcm9tIGJlaW5nIGNvbnNpZGVyZWQgaW4gdGhlIGZpbmFsIERBRy4gVGhpcyBpcyBkb25lIGJ5IHByb3ZpZGluZyBhICoqcmV0YWluIG1hdHJpeCoqIGFuZC9vciBhICoqYmFuIG1hdHJpeCoqLiAKV2Ugd2lsbCBzdGFydCBieSBjcmVhdGluZyB0d28gZW1wdHkgbWF0cmljZXMgd2l0aCB0aGUgc2FtZSBzaXplIGFzIG91ciBkYXRhICg4KSBhbmQgbmFtZWQgcm93cyBhbmQgY29sdW1ucy4KCmBgYHtyIGJhbl9yZXQsIGVjaG89VFJVRX0KCnJldGFpbiA8LSBtYXRyaXgoMCwgbmNvbChhYm5kYXRhKSwgbmNvbChhYm5kYXRhKSkKY29sbmFtZXMocmV0YWluKSA8LSByb3duYW1lcyhyZXRhaW4pIDwtIG5hbWVzKGFibmRhdGEpCgpiYW5uZWQgPC0gbWF0cml4KDAsIG5jb2woYWJuZGF0YSksIG5jb2woYWJuZGF0YSkpCmNvbG5hbWVzKGJhbm5lZCkgPC0gcm93bmFtZXMoYmFubmVkKSA8LSBuYW1lcyhhYm5kYXRhKQoKYGBgCgombmJzcDsKCiMgQmFuIHNvbWUgYXJjcyAob3B0aW9uYWwpClRoZSBpbmZvcm1hdGlvbiBlbmNvZGVkIGluIHRoZSBiYW4gbWF0cml4IGlzICpzdWJqZWN0aXZlbHkgY2hvc2VuKiB0byByZWZsZWN0IG91ciAqKmJlbGllZiBhYm91dCBkYXRhIHN0cnVjdHVyZSoqLiBJbiB0aGlzIGV4YW1wbGUsIGl0IGlzIHJlYXNvbmFibGUgdG8gYXNzdW1lIHRoYXQgbm9uZSBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBtb2RlbCBpcyBnb2luZyB0byBhZmZlY3QgdGhlIGdlbmRlciBvZiB0aGUgYW5pbWFsICh3aGljaCBpcyBhbiBpbmJvcm4gdHJhaXQpLiBUbyBlbmNvZGUgdGhpcyBpbmZvcm1hdGlvbiB3ZSB3aWxsIGJhbiBhbGwgdGhlIGFyY3MgZ29pbmcgdG8gKmZlbWFsZSosIGJ5IHNldHRpbmcgdGhlaXIgdmFsdWUgdG8gMSAoYmFubmVkKSBhcyBvcHBvc2l0ZSB0byB0aGUgZGVmYXVsdCB2YWx1ZSAwIChub24gYmFubmVkKS4gIAoKSG93IGRvZXMgaXQgd29yaz8KClJvd3MgYXJlIGNoaWxkcmVuLCBjb2x1bW5zIHBhcmVudHM6ICAgCmAuICAgYjEgYjIgYjMgYjRgICAKYGIxICAgMCAgMSAgMCAgMGAgIApgYjIgICAwICAwICAwICAwYCAgCmBiMyAgIDEgIDAgIDAgIDBgICAKYGI0ICAgMCAgMCAgMCAgMGAgIAoKU28gYGJhblsxLCAyXSA8LSAxYCAgbWVhbnMgZG8gbm90IGFsbG93IHRoZSBhcmMgZnJvbSBiMiAoc2Vjb25kIGNvbHVtbikgdG8gYjEgKGZpcnN0IHJvdykgYW5kIGBiYW5bMywgMV0gPC0gMWAgbWVhbnMgZG8gbm90IGFsbG93IHRoZSBhcmMgZnJvbSBiMSAoZmlyc3QgY29sdW1uKSB0byBiMyAodGhpcmQgcm93KS4KCk5vdywgd2Ugd2FudCB0byBiYW4gdGhlIGFyY3MgZ29pbmcgZnJvbSBhbnkgdmFyaWFibGUgKD0gYWxsIGNvbHVtbnMgZXhjZXB0IHRoZSB0aGlyZCkgdG8gKmZlbWFsZSogKD0gdGhpcmQgcm93KTogIApgYGB7ciBiYW4sIGVjaG89VFJVRX0KCmJhbm5lZFszLC0zXSA8LSAxCgpgYGAKCkhhdmUgYSBsb29rIGlmIHlvdSB3YW50OiAgCmBgYHtyIGJhbkNoZWNrLCBlY2hvPVRSVUV9CgpiYW5uZWQKCmBgYAoKJm5ic3A7CgojIFRyeSB0byBydW4gb25lIHNlYXJjaCAgCiZuYnNwOwoKIyMgU3RhcnQgd2l0aCAxIHBhcmVudCBhcyBtYXhpbXVtIGxpbWl0CgphYm4gc3VwcG9ydHMgYW4gbWxlIGFuZCBCYXllc2lhbiBpbXBsZW1lbnRhdGlvbi4gV2Ugd2lsbCBydW4gYSBCYXllc2lhbiAoZGVmYXVsdCkgYW5hbHlzaXMuIAoKVG8gbWFrZSB0aGUgc2VhcmNoIG1vcmUgZWZmaWNpZW50LCB3ZSBjb25zdHJhaW4gdGhlIG1heGltdW0gbnVtYmVyIG9mIHBhcmVudHMgYWxsb3dlZCBmb3IgZWFjaCBub2RlLiBXZSB3aWxsIHN0YXJ0IGZyb20gMSBhbmQgaW5jcmVhc2Ugc3Vic2VxdWVudGx5IHVudGlsIHRoZSAqbmV0d29yayBzY29yZSogZG9lcyBub3QgaW1wcm92ZSBmdXJ0aGVyIGV2ZW4gd2hlbiBtb3JlIHBhcmVudHMgYXJlIGFsbG93ZWQuIAoKYGBge3IgNS4xLCBlY2hvPVRSVUV9CgptYXgucGFyIDwtIDEgCgpgYGAKJm5ic3A7CgojIyBCdWlsZCBhIGNhY2hlIG9mIGFsbCBsb2NhbCBjb21wdXRhdGlvbnMKCmBgYHtyIDUuMiwgZWNobz1UUlVFfQpsaWJyYXJ5KGFibikKCm15Y2FjaGUgPC0gYnVpbGRTY29yZUNhY2hlKGRhdGEuZGYgPSBhcy5kYXRhLmZyYW1lKGFibmRhdGEpLCBkYXRhLmRpc3RzID0gZGlzdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhZy5iYW5uZWQgPSBiYW5uZWQsIGRhZy5yZXRhaW5lZCA9IHJldGFpbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heC5wYXJlbnRzID0gbWF4LnBhcikKCmBgYAoKVGhlIGZ1bmN0aW9uICpidWlsZFNjb3JlQ2FjaGUoKSogcmV0dXJucyBhIG5hbWVkIGxpc3Qgb2YgY2xhc3MgKmFibkNhY2hlKiB0aGF0IGNvbnRhaW5zIGFsbCBuZWNlc3NhcnkgZGF0YSBhbmQgaW5mcm9tYXRpb24gdG8gYmUgdXNlZCBpbiAqbW9zdFByb2JhYmxlKCkqLiBGb3IgZWFjaCBub2RlLCB0aGUgKmJ1aWxkU2NvcmVDYWNoZSgpKiBmdW5jdGlvbiB0cmllcyBldmVyeSBjb21iaW5hdGlvbiBvZiBwYXJlbnQgbm9kZXMgKGFsd2F5cyBzYXRpc2Z5aW5nIHRoZSBjb25zdHJhaW50cyAqbWF4LnBhciosICpiYW5uZWQqLCAqcmV0YWluZWQqKSBhbmQgY29tcHV0ZXMgYSBzY29yZS4gVGhlIHBhcmVudCBjaGlsZHJlbiBjb21iaW5hdGlvbnMgYXJlIHRoZW4gc3RvcmVkLCB0b2dldGhlciB3aXRoIHRoZWlyIHNjb3Jlcy4gVGhpcyBvYmplY3QgY29udGFpbnMgYWxsIHRoZSBpbmZvcm1hdGlvbiBuZWVkZWQgZm9yIGRpZmZlcmVudCBmdW5jdGlvbnMsIHRoYXQgdHJ5IHRvIGxlYXJuIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIG5ldHdvcmsuIAoKJm5ic3A7IAoKCiMjIFJ1biB0aGUgZXhhY3Qgc2VhcmNoIGZvciBhIHNwZWNpZmllZCBwYXJlbnQgbGltaXQgIAoKYSkgRmluZCB0aGUgb3B0aW1hbCBEQUcgZ2l2ZW4gdGhlIHBhcmVudCBsaW1pdAoKYGBge3IgNS4zYSwgZWNobz1UUlVFfQpteWRhZyA8LSBtb3N0UHJvYmFibGUoc2NvcmUuY2FjaGUgPSBteWNhY2hlKQpgYGAKICAKYikgRml0IG1hcmdpbmFsIGRlbnNpdGllcwpgYGB7ciA1LjNiLCBlY2hvPVRSVUV9CmZhYm4gPC0gZml0QWJuKG9iamVjdCA9IG15ZGFnKQpgYGAKICAKYykgY2hlY2sgbmV0d29yayBzY29yZQpgYGB7ciA1LjNjLCBlY2hvPVRSVUV9CmZhYm4kbWxpawpgYGAKClRoZSBmdW5jdGlvbiAqbW9zdFByb2JhYmxlKCkqIHJldHVybnMgYW4gb2JqZWN0IG9mIGNsYXNzICphYm5Nb3N0cHJvYmFibGUqLCB3aGljaCBpcyBhIGxpc3QgY29udGFpbmluZzogYSBtYXRyaXggZ2l2aW5nIHRoZSBEQUcgZGVmaW5pdGlvbiBvZiB0aGUgbW9zdCBwcm9iYWJsZSBwb3N0ZXJpb3Igc3RydWN0dXJlLCB0aGUgY2FjaGUgb2YgcHJlLWNvbXB1dGVkIHNjb3JlcyBhbmQgdGhlIHNjb3JlIHVzZWQgZm9yIHNlbGVjdGlvbi4KClRoZSBmdW5jdGlvbiAqZml0QWJuKCkqIHJldHVybnMgYW4gb2JqZWN0IG9mIGNsYXNzICphYm5GaXQqLiBBIG5hbWVkIGxpc3QuIE9uZSBlbnRyeSBmb3IgZWFjaCBvZiB0aGUgdmFyaWFibGVzIGluIGRhdGEuZGYgKGV4Y2x1ZGluZyB0aGUgZ3JvdXBpbmcgdmFyaWFibGUsIGlmIHByZXNlbnQpIHdoaWNoIGNvbnRhaW5zIGFuIGVzdGltYXRlIG9mIHRoZSBsb2cgbWFyZ2luYWwgbGlrZWxpaG9vZCBmb3IgdGhhdCBpbmRpdmlkdWFsIG5vZGUuIEFuIGVudHJ5ICJtbGlrIiB3aGljaCBpcyB0aGUgdG90YWwgbG9nIG1hcmdpbmFsIGxpa2VsaWhvb2QgZm9yIHRoZSBmdWxsIEFCTiBtb2RlbC4gQSB2ZWN0b3Igb2YgZXJyb3IuY29kZXMgLSBub24temVybyBpZiBhIG51bWVyaWNhbCBlcnJvciBvciB3YXJuaW5nIG9jY3VycmVkLCBhbmQgYSB2ZWN0b3IgZXJyb3IuY29kZS5kZXNjIGdpdmluZyBhIHRleHQgZGVzY3JpcHRpb24gb2YgdGhlIGVycm9yLiBBIGxpc3QgbW9kZXMsIHdoaWNoIGNvbnRhaW5zIGFsbCB0aGUgbW9kZSBlc3RpbWF0ZXMgZm9yIGVhY2ggcGFyYW1ldGVyIGF0IGVhY2ggbm9kZS4gQSB2ZWN0b3IgY2FsbGVkIEhlc3NpYW4gYWNjdXJhY3ksIHdoaWNoIGlzIHRoZSBlc3RpbWF0ZWQgbG9jYWwgZXJyb3IgaW4gdGhlIGxvZyBtYXJnaW5hbCBsaWtlbGlob29kIGZvciBlYWNoIG5vZGUuIElmIGNvbXB1dGUuZml4ZWQ9VFJVRSB0aGVuIGEgbGlzdCBlbnRyeSBjYWxsZWQgbWFyZ2luYWxzIHdoaWNoIGNvbnRhaW5zIGEgbmFtZWQgZW50cnkgZm9yIGV2ZXJ5IHBhcmFtZXRlciBpbiB0aGUgQUJOIGFuZCBlYWNoIGVudHJ5IGluIHRoaXMgbGlzdCBpcyBhIHR3by1jb2x1bW4gbWF0cml4IHdoZXJlIHRoZSBmaXJzdCBjb2x1bW4gaXMgdGhlIHZhbHVlIG9mIHRoZSBtYXJnaW5hbCBwYXJhbWV0ZXIsIHNheSB4LCBhbmQgdGhlIHNlY29uZCBjb2x1bW4gaXMgdGhlIHJlc3BlY3RpdmUgZGVuc2l0eSB2YWx1ZSwgcGRmKHgpLiBBbHNvLCBhIGxpc3QgY2FsbGVkIG1hcmdpbmFsLnF1YW50aWxlcyBpcyBwcm9kdWNlZCwgZ2l2aW5nIHRoZSBxdWFudGlsZXMgZm9yIGVhY2ggbWFyZ2luYWwgcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbi4gSWYgY3JlYXRlLmdyYXBoPVRSVUUgdGhlbiBhbiBhZGRpdGlvbmFsIGVudHJ5IGdyYXBoIHdoaWNoIGlzIG9mIHR5cGUgY2xhc3MgKmdyYXBoQU0tY2xhc3MqIChSZ3JhcGh2aXopIGlzIGNyZWF0ZWQuCgombmJzcDsKCiMgUnVuIHRoZSBleGFjdCBzZWFyY2ggYWNyb3NzIGluY3JlbWVudGFsIHBhcmVudCBsaW1pdHMgClJlcGVhdCBzdGVwIDUgZm9yIGluY3JlbWVudGFsIHBhcmVudCBsaW1pdCAoZS5nLiAxIHRvIG5yLnZhci0xKS4gVGhlIG9wdGltYWwgREFHIGlzIHRoZSBvbmUgd2hlcmUgdGhlIG5ldHdvcmsgc2NvcmUgZG9lcyBub3QKaW1wcm92ZSAoaS5lLiBiZWNvbWVzIGJpZ2dlcikgYW55IGxvbmdlciBieSBhbGxvd2luZyBtb3JlIHBhcmVudHMuICAKCmBgYHtyIHNlYXJjaCwgZWNobz1UUlVFfQoKZGF0YWRpciA8LSB0ZW1wZGlyKCkgCgpmb3IgKGkgaW4gMTo3KSB7CiAgbWF4LnBhciA8LSBpCiAgCiAgbXljYWNoZSA8LSBidWlsZFNjb3JlQ2FjaGUoZGF0YS5kZiA9IGFzLmRhdGEuZnJhbWUoYWJuZGF0YSksIGRhdGEuZGlzdHMgPSBkaXN0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYWcuYmFubmVkID0gYmFubmVkLCBkYWcucmV0YWluZWQgPSByZXRhaW4sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heC5wYXJlbnRzID0gbWF4LnBhcikKICAKICBteWRhZyA8LSBtb3N0UHJvYmFibGUoc2NvcmUuY2FjaGUgPSBteWNhY2hlKQogIAogIGZhYm4gPC0gZml0QWJuKG9iamVjdCA9IG15ZGFnKQogIAogIGNhdChwYXN0ZSgibmV0d29yayBzY29yZSBmb3IiLCBpLCAicGFyZW50cyA9IiwgZmFibiRtbGlrLCAiXG5cbiIpKQogICAgICAKICBzYXZlKG15Y2FjaGUsIG15ZGFnLCBmYWJuLCBmaWxlID0gcGFzdGUoZGF0YWRpciwibXBfIiwgbWF4LnBhciwiLlJEYXRhIiwgc2VwPSIiKSkKIAp9CgojIGdldCBuZXR3b3JrIHNjb3JlIGZvciBhbGwgcGFyZW50IGxpbWl0cwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQptcC5tbGlrIDwtIGMoKQpmb3IgKGkgaW4gMTptYXgucGFyKSB7CiAgbG9hZChwYXN0ZShkYXRhZGlyLCJtcF8iLCBpLCIuUkRhdGEiLCBzZXA9IiIpKQogIG1wLm1saWsgPC0gYyhtcC5tbGlrLCBmYWJuJG1saWspCn0KCmBgYAoKJm5ic3A7CgojIFBsb3QgbG9nIG1hcmdpbmFsIGxpa2VsaWhvb2QgaW4gZnVuY3Rpb24gb2YgdGhlIHBhcmVudCBsaW1pdAoKYGBge3IgbWxpaywgZWNobz1UUlVFfQoKIyBjaGVjayBob3cgaXQgbG9va3MKIyAtLS0tLS0tLS0tLS0tLS0tLS0KcGxvdCgxOm1heC5wYXIsIG1wLm1saWssIHhsYWIgPSAiUGFyZW50IGxpbWl0IiwgeWxhYiA9ICJMb2cgbWFyZ2luYWwgbGlrZWxpaG9vZCIsIAogICAgIHR5cGUgPSAiYiIsIGNvbD0icmVkIiwgeWxpbT1yYW5nZShtcC5tbGlrKSkKYWJsaW5lKHY9d2hpY2gobXAubWxpaz09bWF4KG1wLm1saWspKVsxXSwgY29sPSJncmV5IiwgbHR5PTIpCgpgYGAKCkFmdGVyIG1heC5wYXI9YHIgd2hpY2gobXAubWxpaz09bWF4KG1wLm1saWspKVsxXWAgdGhlIG1heGltdW0gbG9nIG1hcmdpbmFsIGxpa2VsaWhvb2QgaXMgY29uc3RhbnQ6ICAKCmBgYHtyIG1saWsyLCBlY2hvPUZBTFNFfQoKbXAubWxpawogCmBgYAoKJm5ic3A7CgpIZXJlIGlzIHdoYXQgdGhlIGJlc3QgZml0dGluZyBEQUcgbG9va3MgbGlrZSB1c2luZyAqcGxvdEFibigpKiBmdW5jdGlvbjoKCmBgYHtyIERBR3Nob3d9CgpwbG90QWJuKG15ZGFnKQoKYGBgCgombmJzcDsKCkJlZm9yZSBnb2luZyBhaGVhZCBpbnRlcnByZXRpbmcgdGhlIHJlc3VsdHMsIHdlIG5lZWQgb25lIG1vcmUgc3RlcC4gU28gZmFyLCB3ZSBoYXZlIGlkZW50aWZpZWQgYSBEQUcgd2hpY2ggaGFzIHRoZSBtYXhpbXVtIHBvc3NpYmxlIGdvb2RuZXNzIG9mIGZpdCBhY2NvcmRpbmcgdG8gdGhlIGxvZyBtYXJnaW5hbCBsaWtlbGlob29kLiBUaGlzIGlzIHRoZSBzdGFuZGFyZCBnb29kbmVzcyBvZiBmaXQgbWV0cmljIGluIEJheWVzaWFuIG1vZGVsbGluZyBhbmQgaW5jbHVkZXMgYW4gaW1wbGljaXQgcGVuYWx0eSBmb3IgbW9kZWwgY29tcGxleGl0eS4gSG93ZXZlciwgdGhlIGxvZyBtYXJnaW5hbCBsaWtlbGlob29kIGlzIGFsc28ga25vd24gdG8gYmUgcHJvbmUgdG8gKipvdmVyZml0dGluZyoqIChlc3BlY2lhbGx5IHdpdGggc21hbGxlciBkYXRhIHNldHMpLCBtZWFuaW5nIHRoYXQgaXQgbWF5IGlkZW50aWZ5IG1vcmUgcGFyYW1ldGVycyB0aGFuIGNhbiBiZSBhY3R1YWxseSBqdXN0aWZpZWQgYnkgdGhlIGRhdGEuIFRoZXJlZm9yZSwgaXQgaXMgYWR2aXNhYmxlIHRvIGNoZWNrIGFuZCBhZGRyZXNzIHBvdGVudGlhbCBvdmVyZml0dGluZyBiZWZvcmUgZHJhd2luZyBhbnkgY29uY2x1c2lvbiBiYXNlZCBvbiB0aGUgbW9kZWwgcmVzdWx0cy4gCgpBIHdlbGwgZXN0YWJsaXNoZWQgYXBwcm9hY2ggZm9yIGFkZHJlc3Npbmcgb3ZlcmZpdHRpbmcgaXMgdG8gdXNlICoqcGFyYW1ldHJpYyBib290c3RyYXBwaW5nKiouIEJhc2ljYWxseSwgdGhlIG1vZGVsIGNob3NlbiBmcm9tIHRoZSBleGFjdCBzZWFyY2ggd2lsbCBiZSB1c2VkIHRvIGdlbmVyYXRlIG1hbnkgYm9vdHN0cmFwIGRhdGFzZXRzIChlLmcuIDEwMDApIG9mIGVxdWFsIHNpemUgdG8gdGhlIG9yaWdpbmFsIGRhdGFzZXQgKG49YHIgbnJvdyhhYm5kYXRhKWAgaW4gb3VyIGNhc2UpLiBFYWNoIGJvb3RzdHJhcCBkYXRhc2V0IHdpbGwgYmUgdGhlbiB0cmVhdGVkIGFzIGlmIGl0IHdlcmUgdGhlIG9yaWdpbmFsIGRhdGEsIGFuZCBhIGdsb2JhbGx5IG9wdGltYWwgREFHIHdpbGwgYmUgaWRlbnRpZmllZCBleGFjdGx5IGFzIGRlc2NyaWJlZCBiZWZvcmUgKGkuZS4gZXhhY3Qgc2VhcmNoKS4gV2Ugd2lsbCB0aGVyZWZvcmUgZ2V0IGFzIG1hbnkgREFHcyBhcyB0aGUgbnVtYmVyIG9mIHNpbXVsYXRlZCBkYXRhc2V0cyAoZWcuIDEwMDApLiBUbyBhZGRyZXNzIG92ZXJmaXR0aW5nLCBhbnkgYXJjcyBpbiB0aGUgREFHIGZyb20gdGhlIG9yaWdpbmFsIGRhdGEgd2hpY2ggd2lsbCBub3QgYmUgcmVjb3ZlcmVkIGluID4gNTAlIG9mIHRoZSBib290c3RyYXAgREFHcyB3aWxsIGJlIGRlZW1lZCB0byBoYXZlIGluc3VmZmljaWVudCBzdGF0aXN0aWNhbCBzdXBwb3J0IHRvIGJlIGNvbnNpZGVyZWQgcm9idXN0LiAKCiZuYnNwOwoKIyBQYXJhbWV0cmljIGJvb3RzdHJhcHBpbmcgCgpUaGlzIHN0ZXAgd2lsbCBiZSBkb25lIHdpdGggdGhlIGFpZCBvZiBhIHNvZnR3YXJlIGZvciBCYXllc2lhbiBzdGF0aXN0aWNhbCBhbmFseXNpcyB1c2luZyBNYXJrb3YgQ2hhaW4gTW9udGUgQ2FybG8gKE1DTUMpIHNpbXVsYXRpb25zICh3ZSB3aWxsIHVzZSAqKkpBR1MqKiBbQHBsdW1tZXIyMDAzamFnc10sIGJ1dCBhbnkgb3RoZXIgaXMgZmluZSkuIFdlIHdpbGwgdXNlIHRoZSBwYXJhbWV0ZXJzIGVzdGltYXRlZCBmcm9tIG91ciBtb2RlbCB0byBidWlsZCBhICpCVUcqIG1vZGVsIHRvIHNpbXVsYXRlIGRhdGEuIEluIG90aGVyIHdvcmRzLCB3ZSB3aWxsIGRvIHRoZSByZXZlcnNlIHByb2Nlc3M6IGluc3RlYWQgb2YgdXNpbmcgZGF0YSB0byBlc3RpbWF0ZSBwYXJhbWV0ZXJzLCB3ZSB3aWxsIHVzZSBwYXJhbWV0ZXJzIHRvIGVzdGltYXRlIGRhdGEuIAoKJm5ic3A7CgojIyBFeHRyYWN0IHBhcmFtZXRlcnMgZnJvbSBiZXN0IGZpdHRpbmcgbW9kZWwgYW5kIHNhdmUgdGhlbSBmb3IgTUNNQyBzaW11bGF0aW9ucyAgCgpUaGUgcGFyYW1ldGVycyBhdCBlYWNoIG5vZGUgYXJlIGVzdGltYXRlZCBhcyBwb3N0ZXJpb3IgcHJvYmFiaWxpdHkgZGVuc2l0eSBkaXN0cmlidXRpb25zLiBUaGVzZSBtYXJnaW5hbCBkZW5zaXRpZXMgYXJlIHRoZSBvbmVzIHdoZXJlIEpBR1MgbmVlZHMgdG8gc2FtcGxlIGZyb20gaW4gb3JkZXIgdG8gc2ltdWxhdGUgZGF0YS4gSW4gb3JkZXIgdG8gdXNlIHRoZXNlIGRpc3RyaWJ1dGlvbnMgd2l0aCBKQUdTLCB0aGUgZGVuc2l0aWVzIG5lZWQgdG8gYmUgYXBwcm94aW1hdGVkIGJ5IGEgZGlzY3JldGUgZGlzdHJpYnV0aW9uIG92ZXIgYSBmaW5lIGFuZCBlcXVhbGx5IHNwYWNlZCBncmlkLiBIZXJlIGlzIGEgbW9jayBleGFtcGxlIGZvciBhIGh5cG90aGV0aWNhbCBwYXJhbWV0ZXIgJFxiZXRhJDoKCmBgYHtyIGV4R3JpZCwgLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD04fQoKdjEgPC0gcm5vcm0oMTAwMDAsIDAsIDEpCnBhcihtZnJvdz1jKDEsMiksIG1hcj1jKDQsNCwyLDEpKSAKCnBsb3QoZGVuc2l0eSh2MSksIG1haW49Ik1hcmdpbmFsIGRlbnNpdHkiLCB4bGFiPWV4cHJlc3Npb24ocGFzdGUoInBhcmFtZXRlciAiLCBiZXRhKSksIAogICAgIGNleC5heGlzPTAuOSwgY2V4Lm1haW49MSkKaGlzdCh2MSwgYnJlYWtzPTEwMCwgbWFpbj0iRGlzY3JldGl6YXRpb24gb2YgbWFyZ2luYWwgZGVuc2l0eSIsIHhsYWI9ZXhwcmVzc2lvbihwYXN0ZSgicGFyYW1ldGVyICIsIGJldGEpKSwgCiAgICAgY2V4LmF4aXM9MC45LCBjZXgubWFpbj0xKQoKYGBgCgombmJzcDsKCmBgYHtyIHByZXBCb290LCBlY2hvPVRSVUV9CiMgRml0IG1hcmdpbmFsIGRlbnNpdGllcyBvdmVyIGEgZml4ZWQgZ3JpZCAtLT4gbi5ncmlkPTEwMDAKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogIG1hcmcuZiA8LSBmaXRBYm4ob2JqZWN0ID0gbXlkYWcsIGNvbXB1dGUuZml4ZWQ9VFJVRSwgbi5ncmlkPTEwMDApCgojIEV4dHJhY3QgdmFsdWVzIAojIC0tLS0tLS0tLS0tLS0tCiAgbSA8LSBtYXJnLmYkbWFyZ2luYWxzW1sxXV0gCiAgZm9yKGkgaW4gMjogbGVuZ3RoKG1hcmcuZiRtYXJnaW5hbHMpKQogIHsgbSA8LSBjKG0sIG1hcmcuZiRtYXJnaW5hbHNbW2ldXSl9CiAgCiMgQmluZCBhbGwgdGhlIG1hcmdpbmFscyBmb3IgdGhlIHNhbWUgbm9kZSBpbnRvIGEgbWF0cml4CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgQVIucCA8LSBjYmluZCggbVtbICJBUnwoSW50ZXJjZXB0KSJdXSwgbVtbICJBUnxhZ2UiXV0pCiAgcG5ldW1TLnAgPC0gY2JpbmQoIG1bWyAicG5ldW1TfChJbnRlcmNlcHQpIl1dLCBtW1sgInBuZXVtU3xhZ2UiXV0pCiAgZmVtYWxlLnAgPC0gY2JpbmQoIG1bWyAiZmVtYWxlfChJbnRlcmNlcHQpIl1dKQogIGxpdmRhbS5wIDwtIGNiaW5kKCBtW1sgImxpdmRhbXwoSW50ZXJjZXB0KSJdXSwgbVtbICJsaXZkYW18ZWdncyJdXSkKICBlZ2dzLnAgPC0gY2JpbmQoIG1bWyAiZWdnc3woSW50ZXJjZXB0KSJdXSwgbVtbICJlZ2dzfGFkZyJdXSkKICB3b3JtQ291bnQucCA8LSBjYmluZCggbVtbICJ3b3JtQ291bnR8KEludGVyY2VwdCkiXV0sIG1bWyAid29ybUNvdW50fEFSIl1dLAogICAgICAgICAgICAgICAgICAgICAgICBtW1sgIndvcm1Db3VudHxlZ2dzIl1dLCBtW1sgIndvcm1Db3VudHxhZ2UiXV0sIG1bWyAid29ybUNvdW50fGFkZyJdXSkKICBhZ2UucCA8LSBjYmluZCggbVtbICJhZ2V8KEludGVyY2VwdCkiXV0sIG1bWyAiYWdlfGZlbWFsZSJdXSkKICBwcmVjLmFnZS5wIDwtIGNiaW5kKCBtW1sgImFnZXxwcmVjaXNpb24iIF1dKQogIGFkZy5wIDwtIGNiaW5kKCBtW1sgImFkZ3woSW50ZXJjZXB0KSJdXSwgbVtbICJhZGd8YWdlIl1dKQogIHByZWMuYWRnLnAgPC0gY2JpbmQoIG1bWyAiYWRnfHByZWNpc2lvbiIgXV0pCgojIFNhdmUgaXQgdG8gYSBmaWxlIG5hbWVkIFBvc3RQYXJhbXMgdG8gYmUgcmVhZCBieSBKQUdTCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KICBkdW1wKGMoIkFSLnAiLCAicG5ldW1TLnAiLCAiZmVtYWxlLnAiLCAibGl2ZGFtLnAiLCAiZWdncy5wIiwgCiAgICAgICAid29ybUNvdW50LnAiLCAiYWdlLnAiLCAicHJlYy5hZ2UucCIsICJhZGcucCIsICJwcmVjLmFkZy5wIiksCiAgICAgZmlsZT0iUG9zdFBhcmFtcy5SIikKCmBgYAoKJm5ic3A7CgojIyBXcml0ZSB0aGUgQlVHIG1vZGVsCkluIG9yZGVyIHRvIHNpbXVsYXRlIHRoZSB2YXJpYWJsZXMgb2Ygb3VyIGRhdGFzZXQgd2UgbmVlZCB0byBwcm92aWRlIGEgbW9kZWwgZm9yIGVhY2ggb2YgdGhlbSwgdXNpbmcgdGhlIGFmb3JlbWVudGlvbmVkIHBhcmFtZXRlcnMgZXN0aW1hdGVzLiBGb3IgaW5zdGFuY2UsIHRoZSBiaW5vbWlhbCBub2RlICpBUiogaW4gb3VyIERBRyBoYXMgb25lIGluY29taW5nIGFyYyBjb21pbmcgZnJvbSB0aGUgbm9kZSAqYWdlKi4gSW4gYSByZWdyZXNzaW9uIG5vdGF0aW9uIHRoaXMgd291bGQgYmUgdHJhbnNsYXRlZCBpbnRvOiAgCgo8cCBzdHlsZT0idGV4dC1hbGlnbjogY2VudGVyOyI+bG9naXQoKkFSKikgPSAkXGFscGhhJCArICRcYmV0YSQgeCAqYWdlKiArICRcZXBzaWxvbiQ8L2E+PC9wPgogICAgICAgIAogICAgICAKd2hlcmUgJFxhbHBoYSQgaXMgdGhlIGludGVyY2VwdCBhbmQgJFxiZXRhJCB0aGUgcmVncmVzc2lvbiBjb2VmZmljaWVudCBmb3IgdmFyaWFibGUgKmFnZSogYW5kICRcZXBzaWxvbiQgaXMgdGhlIGVycm9yIHRlcm0gbW9kZWxsZWQgYnkgYSBiaW5vbWlhbCBkaXN0cmlidXRpb24uICAKCkdpdmVuIHRoYXQgd2Ugd2lsbCBzaW11bGF0ZSB0aGUgZGF0YSBpbiBhIEJheWVzaWFuIGZyYW1ld29yaywgQVIgd2lsbCBiZSBtb2RlbGxlZCBhcyBhIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbi4gVGhlcmVmb3JlIGl0IHdpbGwgbG9vayBsaWtlOiAgCgpgQVIgfiBkYmVybihwcm9iQVIpO2AgIApgbG9naXQocHJvYkFSKTwtIGFscGhhICsgYmV0YSphZ2U7YCAgCgpUaGVuLCB0aGUgdmFsdWVzIG9mIGFscGhhIGFuZCBiZXRhIHdpbGwgYmUgc2FtcGxlZCAoZGNhdCkgZnJvbSBvdXIgZGlzY3JldGUgZGlzdHJpYnV0aW9uIG9mIHBhcmFtZXRlcnM6ICAKCmBhbHBoYS5wcm9iIH4gZGNhdChBUi5wWyAsMl0pO2AgLS0+IHNhbXBsZSBmcm9tIHRoZSB2ZWN0b3Igb2YgZGVuc2l0eSB2YWx1ZXMgZih4KSAoc2Vjb25kIGNvbHVtbiBpbiBtYXRyaXgpICAKYGFscGhhIH4gQVIucFthbHBoYS5wcm9iLDFdO2AgICAtLT4gY29ycmVzcG9uZGluZyB4IHZhbHVlIGZvciB0aGUgc2FtcGxlZCBkZW5zaXR5IChmaXJzdCBjb2x1bW4gaW4gbWF0cml4KSAgCmBiZXRhLnByb2IgfiBkY2F0KEFSLnBbICw0XSk7YCAgCmBiZXRhIH4gQVIucFtiZXRhLnByb2IsMV07YCAgIAogCiZuYnNwOyAKClRoZSBCVUcgZmlsZSAoKm1vZGVsOHZQb2lzLmJ1ZyopIGNhbiBiZSByZXRyaWV2ZWQgZnJvbSB0aGUgKmZpbGUqIGRpcmVjdG9yeS4KCiZuYnNwOwoKIyMgUnVuIEpBR1MgYW5kIGluc3BlY3QgdGhlIHJlc3VsdCBvZiBhIHNpbXVsYXRlZCBkYXRhc2V0CgpgYGB7ciB0ZXN0SkFHUywgZWNobz1UUlVFfQoKbGlicmFyeShyamFncykKCiMgc2V0IGluaXRzCiMgLS0tLS0tLS0tCmluaXQgPC0gbGlzdCgiLlJORy5uYW1lIj0iYmFzZTo6TWVyc2VubmUtVHdpc3RlciIsICIuUk5HLnNlZWQiPTQyKQoKIyBsb2FkIGRhdGEKIyAtLS0tLS0tLS0Kc291cmNlKCJQb3N0UGFyYW1zLlIiKQoKIyBydW4gbW9kZWwgb25jZQojIC0tLS0tLS0tLS0tLS0tCmpqIDwtIGphZ3MubW9kZWwoZmlsZSA9ICJtb2RlbDh2UG9pcy5idWciLCAKICAgICAgICAgICAgICAgICBkYXRhID0gbGlzdCggICdBUi5wJz1BUi5wICwgJ3BuZXVtUy5wJz1wbmV1bVMucCAsICdmZW1hbGUucCc9ZmVtYWxlLnAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2xpdmRhbS5wJz1saXZkYW0ucCAsICdlZ2dzLnAnPWVnZ3MucCAsICd3b3JtQ291bnQucCc9d29ybUNvdW50LnAgLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZ2UucCc9YWdlLnAgLCdwcmVjLmFnZS5wJz1wcmVjLmFnZS5wLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZGcucCc9YWRnLnAgLCAncHJlYy5hZGcucCc9cHJlYy5hZGcucCksCiAgICAgICAgICAgICAgICAgaW5pdHMgPSBpbml0LAogICAgICAgICAgICAgICAgIG4uY2hhaW5zID0gMSwgCiAgICAgICAgICAgICAgICAgbi5hZGFwdCA9IDUwMCkKCiMgcnVuIG1vcmUgaXRlcmF0aW9ucwojIC0tLS0tLS0tLS0tLS0tLS0tLS0KdXBkYXRlKGpqLCAxMDAwMDApCgojIHNldCBudW1iZXIgb2Ygb2JzZXJ2YXRpb24gd2Ugd2FudCB0byBleHRyYWN0IGZvciBhIGRhdGFzZXQgCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpuLm9icz0zNDEKCiMgc2FtcGxlIGRhdGEgKHNhbWUgc2l6ZSBhcyBvcmlnaW5hbDogMzQxKSB3aXRoIGEgc2FtcGxpbmcgbGFnICgyMCkgdG8gcmVkdWNlIGF2b2lkIGF1dG9jb3JyZWxhdGlvbgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0Kc2FtcCA8LSBjb2RhLnNhbXBsZXMoamosIGMoIkFSIiwgInBuZXVtUyIsICJmZW1hbGUiLCAibGl2ZGFtIiwgImVnZ3MiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIndvcm1Db3VudCIsICJhZ2UiLCAicHJlYy5hZ2UiLCAiYWRnIiwgInByZWMuYWRnIiksCiAgICAgICAgICAgICAgICAgICAgIG4uaXRlcj0gbi5vYnMqMjAgLCB0aGluID0yMCkKCmBgYAoKTm93IGNvbXBhcmUgdGhlIHNpbXVsYXRlZCBkYXRhIHdpdGggdGhlIG9yaWdpbmFsIGRhdGEuIE9ic2VydmUgdGhhdCBHYXVzc2lhbiBub2RlcyBhcmUgYnkgZGVmYXVsdCBjZW50cmVkIHdoZW4gZG9pbmcgYWJuIHNlYXJjaCwgbWVhbmluZyB0aGF0IHRoZSBzaW11bGF0ZWQgZGF0YSBmb3IgdGhvc2Ugbm9kZXMgd2lsbCBiZSBjZW50cmVkIGFzIHdlbGwuIAoKYGBge3IgdGVzdEpBR1MyLCBlY2hvPUZBTFNFLCBmaWcuaGVpZ2h0PTIsIGZpZy53aWR0aD0xMH0KIyBleHRyYWN0IHBvc3RlcmlvciBkZW5zaXRpZXMgYW5kIHB1dCBpbiBhIGRhdGFmcmFtZQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnBvc3QuZHQgPC0gZGF0YS5mcmFtZShBUiA9IHVubGlzdChzYW1wWywiQVIiXSksCiAgICAgICAgICAgICAgICAgICAgICBwbmV1bVMgPSB1bmxpc3Qoc2FtcFssInBuZXVtUyJdKSwKICAgICAgICAgICAgICAgICAgICAgIGZlbWFsZSA9IHVubGlzdChzYW1wWywiZmVtYWxlIl0pLAogICAgICAgICAgICAgICAgICAgICAgbGl2ZGFtID0gdW5saXN0KHNhbXBbLCJsaXZkYW0iXSksCiAgICAgICAgICAgICAgICAgICAgICBlZ2dzID0gdW5saXN0KHNhbXBbLCJlZ2dzIl0pLAogICAgICAgICAgICAgICAgICAgICAgd29ybUNvdW50ID0gdW5saXN0KHNhbXBbLCJ3b3JtQ291bnQiXSksCiAgICAgICAgICAgICAgICAgICAgICBhZ2UgPSB1bmxpc3Qoc2FtcFssImFnZSJdKSwKICAgICAgICAgICAgICAgICAgICAgIGFkZyA9IHVubGlzdChzYW1wWywiYWRnIl0pKQoKCiMgY29tcGFyZSB3aXRoIG9yaWdpbmFsIGRhdGEKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpkdDwtYWJuZGF0YQoKIyBnZXQgY2VudGVyZWQgdmVyc2lvbiBvZiBhZ2UgYW5kIGFkZyB0byBjb21wYXJlIHRvIGJvb3RzdHJhcCBkYXRhCmR0JGFnZS5jIDwtIChkdCRhZ2UgLSBtZWFuKGR0JGFnZSkpL3NkKGR0JGFnZSkgCmR0JGFkZy5jIDwtIChkdCRhZGcgLSBtZWFuKGR0JGFkZykpL3NkKGR0JGFkZykgCgojIENvbXBhcmUgZGlzdHJpYnV0aW9uIG9mIG9yaWdpbmFsIGFuZCBzaW11bGF0ZWQgZGF0YQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpwYXIobWZyb3c9YygxLDQpLCBtYXI9YygyLDIsMS41LDEpKQoKYmFycGxvdCh0YWJsZShkdCRBUikvMzQxLCB5bGltPWMoMCwxKSwgbWFpbj0iQVIgLSBvcmlnaW5hbCIpCmJhcnBsb3QodGFibGUocG9zdC5kdCRBUikvMzQxLCAgeWxpbT1jKDAsMSksIG1haW49IkFSIC0gc2ltdWxhdGVkIiwgCiAgICAgICAgY29sLm1haW4gPSAiYmx1ZSIsIGJvcmRlcj0iYmx1ZSIpIAoKYmFycGxvdCh0YWJsZShkdCRwbmV1bVMpLzM0MSwgeWxpbT1jKDAsMSksIG1haW49InBuZXVtUyAtIG9yaWdpbmFsIikKYmFycGxvdCh0YWJsZShwb3N0LmR0JHBuZXVtUykvMzQxLCAgeWxpbT1jKDAsMSksIG1haW49InBuZXVtUyAtIHNpbXVsYXRlZCIsIAogICAgICAgIGNvbC5tYWluID0gImJsdWUiLCBib3JkZXI9ImJsdWUiKSAKCmJhcnBsb3QodGFibGUoZHQkZmVtYWxlKS8zNDEsIHlsaW09YygwLDEpLCBtYWluPSJmZW1hbGUgLSBvcmlnaW5hbCIpCmJhcnBsb3QodGFibGUocG9zdC5kdCRmZW1hbGUpLzM0MSwgIHlsaW09YygwLDEpLCBtYWluPSJmZW1hbGUgLSBzaW11bGF0ZWQiLCAKICAgICAgICBjb2wubWFpbiA9ICJibHVlIiwgYm9yZGVyPSJibHVlIikgCgpiYXJwbG90KHRhYmxlKGR0JGxpdmRhbSkvMzQxLCB5bGltPWMoMCwxKSwgbWFpbj0ibGl2ZGFtIC0gb3JpZ2luYWwiKQpiYXJwbG90KHRhYmxlKHBvc3QuZHQkbGl2ZGFtKS8zNDEsICB5bGltPWMoMCwxKSwgbWFpbj0ibGl2ZGFtIC0gc2ltdWxhdGVkIiwgCiAgICAgICAgY29sLm1haW4gPSAiYmx1ZSIsIGJvcmRlcj0iYmx1ZSIpCgpiYXJwbG90KHRhYmxlKGR0JGVnZ3MpLzM0MSwgeWxpbT1jKDAsMSksIG1haW49ImVnZ3MgLSBvcmlnaW5hbCIpCmJhcnBsb3QodGFibGUocG9zdC5kdCRlZ2dzKS8zNDEsICB5bGltPWMoMCwxKSwgbWFpbj0iZWdncyAtIHNpbXVsYXRlZCIsIAogICAgICAgIGNvbC5tYWluID0gImJsdWUiLCBib3JkZXI9ImJsdWUiKSAKCmhpc3QoZHQkd29ybUNvdW50LCB4bGFiPSIiLCBtYWluPSJ3b3JtQ291bnQgLSBvcmlnaW5hbCIsCiAgICAgcHJvYj1UUlVFLGNvbD0iZ3JleSIsYm9yZGVyPSJ3aGl0ZSIsIHlsaW09YygwLDAuNikpCiAgICAgbGluZXMoZGVuc2l0eShkdCR3b3JtQ291bnQpLGx3ZD0xLjUpCiAgICAgCmhpc3QocG9zdC5kdCR3b3JtQ291bnQsIHhsYWI9IiIsIG1haW49Indvcm1Db3VudCAtIHNpbXVsYXRlZCIsIGNvbC5tYWluID0gImJsdWUiLCAKICAgICBwcm9iPVRSVUUsY29sPSJncmV5Iixib3JkZXI9IndoaXRlIiwgeGxpbT1jKDAsODApLCB5bGltPWMoMCwwLjYpKQogICAgIGxpbmVzKGRlbnNpdHkocG9zdC5kdCR3b3JtQ291bnQpLGx3ZD0xLjUsIGNvbD0iYmx1ZSIpCgpoaXN0KGR0JGFnZS5jLCB4bGFiPSIiLCBtYWluPSJhZ2UgLSBvcmlnaW5hbCIsCiAgICAgcHJvYj1UUlVFLGNvbD0iZ3JleSIsYm9yZGVyPSJ3aGl0ZSIpCiAgICAgbGluZXMoZGVuc2l0eShkdCRhZ2UuYyksbHdkPTEuNSkKaGlzdChwb3N0LmR0JGFnZSwgeGxhYj0iIiwgbWFpbj0iYWdlIC0gc2ltdWxhdGVkIiwgY29sLm1haW4gPSAiYmx1ZSIsCiAgICAgcHJvYj1UUlVFLGNvbD0iZ3JleSIsYm9yZGVyPSJ3aGl0ZSIpCiAgICAgbGluZXMoZGVuc2l0eShwb3N0LmR0JGFnZSksbHdkPTEuNSwgY29sPSJibHVlIikKCmhpc3QoZHQkYWRnLmMsIHhsYWI9IiIsIG1haW49ImFkZyAtIG9yaWdpbmFsIiwKICAgICBwcm9iPVRSVUUsY29sPSJncmV5Iixib3JkZXI9IndoaXRlIikKICAgICBsaW5lcyhkZW5zaXR5KGR0JGFkZy5jKSxsd2Q9MS41KQpoaXN0KHBvc3QuZHQkYWRnLCB4bGFiPSIiLCBtYWluPSJhZGcgLSBzaW11bGF0ZWQiLCBjb2wubWFpbiA9ICJibHVlIiwKICAgICBwcm9iPVRSVUUsY29sPSJncmV5Iixib3JkZXI9IndoaXRlIikKICAgICBsaW5lcyhkZW5zaXR5KHBvc3QuZHQkYWRnKSxsd2Q9MS41LCBjb2w9ImJsdWUiKQoKYGBgCgpTaW11bGF0ZWQgZGF0YSBsb29rcyBmYWlybHkgT0sgKHBlcmhhcHMgd29ybUNvdW50IGlzIHN1Yi1vcHRpbWFsIGFzIHRoZSBzaW11bGF0ZWQgZGF0YSBtaXNzIHRvIHJlcHJlc2VudCB0aGUgbG9uZyByaWdodCB0YWlsKSBzbyB3ZSBjYW4gcHJvY2VlZCB3aXRoIHRoZSBib290c3RyYXBwaW5nLiAgCiAKJm5ic3A7CgojIyBJdGVyYXRlIGRhdGFzZXQgc2ltdWxhdGlvbiArIGV4YWN0IHNlYXJjaCBvdmVyIGFuZCBvdmVyCgpXaGF0IHdlIHdpbGwgZG8gaXMgdG8gY3JlYXRlIGEgbG9vcCB0byAxKSBzaW11bGF0ZSBkYXRhLCAyKSBkbyBleGFjdCBzZWFyY2ggb24gc3VjaCBkYXRhLCBhbmQgMykgc3RvcmUgdGhlIGJlc3QgZml0dGluZyBEQUcgIG92ZXIgYW5kIG92ZXIgZm9yIG1hbnkgdGltZXMgKGUuZy4gMTAwMCBpdGVyYXRpb25zKS4gQm9vdHN0cmFwIGRhdGEgbmVlZCB0byBiZSBzYXZlZCBpbiBhIGZvbGRlciB0byBiZSBmdXJ0aGVyIGluc3BlY3RlZC4KCmBgYHtyIGJvb3RBQk4sIGVjaG89VFJVRSwgZXZhbD1GQUxTRX0KCiMgbG9hZCBkYXRhIApkYXRhKCJhZGciLCBwYWNrYWdlID0gImFibiIpCgpkZiA8LSBhZGcgJT4lCiAgZHBseXI6OnNlbGVjdCgtZmFybSkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpCgp2YXJzIDwtIGNvbG5hbWVzKGRmKQoKIyBsb2FkIGRhdGEgZm9yIGphZ3MKc291cmNlKCJQb3N0UGFyYW1zLlIiKQoKIyBzZWxlY3QgbnIuIGJvb3RzdHJhcCBzYW1wbGVzIHRvIHJ1bgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnNldC5zZWVkKDQ2ODQ2KQoKbiA8LSBzYW1wbGUoMToxMDAwMDAsIDEwMDApCgoKIyBzcGVjaWZ5IG1heCBudW1iZXIgb2YgcGFyZW50cyBiYXNlZCBvbiBwcmV2aW91cyBzZWFyY2gKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KbWF4LnBhciA8LSA0CgoKIyBTaW11bGF0ZSBkYXRhIGFuZCBydW4gQUJOIG9uIHN1Y2ggZGF0YXNldAojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmJvb3Quc2F2ZSA8LSBsaXN0KCkKCmZvciAoaSBpbiAxOmxlbmd0aChuKSkgewoKICBwcmludChwYXN0ZSgiUnVubmluZyBzaW11bGF0aW9uIiwgaSkpCgogICMgcGljayBpbml0aWFscwogIGluaXQgPC0gbGlzdCgiLlJORy5uYW1lIj0iYmFzZTo6TWVyc2VubmUtVHdpc3RlciIsICIuUk5HLnNlZWQiPW5baV0pCgogICMgcnVuIG1vZGVsCiAgamogPC0gamFncy5tb2RlbChmaWxlID0gIm1vZGVsOHZQb2lzLmJ1ZyIsCiAgICAgICAgICAgICAgICAgICBkYXRhID0gbGlzdCggICdBUi5wJz1BUi5wICwgJ3BuZXVtUy5wJz1wbmV1bVMucCAsICdmZW1hbGUucCc9ZmVtYWxlLnAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdsaXZkYW0ucCc9bGl2ZGFtLnAgLCAnZWdncy5wJz1lZ2dzLnAgLCAnd29ybUNvdW50LnAnPXdvcm1Db3VudC5wICwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2FnZS5wJz1hZ2UucCAsJ3ByZWMuYWdlLnAnPXByZWMuYWdlLnAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhZGcucCc9YWRnLnAgLCAncHJlYy5hZGcucCc9cHJlYy5hZGcucCksCiAgICAgICAgICAgICAgICAgICBpbml0cyA9IGluaXQsCiAgICAgICAgICAgICAgICAgICBuLmNoYWlucyA9IDEsCiAgICAgICAgICAgICAgICAgICBuLmFkYXB0ID0gNTAwKQoKICAjIHJ1biBtb3JlIGl0ZXJhdGlvbnMKICB1cGRhdGUoamosIDEwMDAwMCkKCiAgIyBzYW1wbGUgZGF0YSAoc2FtZSBzaXplIGFzIG9yaWdpbmFsOiAzNDEpIHdpdGggYSBzYW1wbGluZyBsYWcgKDIwKSB0byByZWR1Y2UgYXZvaWQgYXV0b2NvcnJlbGF0aW9uCiAgc2FtcCA8LSBjb2RhLnNhbXBsZXMoamosIGMoIkFSIiwgInBuZXVtUyIsICJmZW1hbGUiLCAibGl2ZGFtIiwgImVnZ3MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ3b3JtQ291bnQiLCAiYWdlIiwgInByZWMuYWdlIiwgImFkZyIsICJwcmVjLmFkZyIpLAogICAgICAgICAgICAgICAgICAgICAgIG4uaXRlcj0gNjgyMCAsIHRoaW4gPTIwKQoKICAjIGJ1aWxkIGRhdGFmcmFtZSBpbiB0aGUgc2FtZSBzaGFwZSBhcyB0aGUgb3JpZ2luYWwgb25lCiAgZHQuYm9vdCA8LSBhcy5kYXRhLmZyYW1lKGFzLm1hdHJpeChzYW1wKSkgIyBwYXkgYXR0ZW50aW9uIGF0IG9yZGVyIG5hbWVzCgogIGR0LmJvb3Q8LSBkdC5ib290WywgdmFyc10KCgogICMgbm93IGNvZXJjZSB0byBmYWN0b3JzIGlmIG5lZWQgYmUgYW5kIHNldCBsZXZlbHMgLSBOT1RFUyBzZXR0aW5nIGxldmVscyB3b3JrcyBhcwogICMgIjAiICIxIiBpcyBpbiB0aGUgc2FtZSBvcmRlciBhcyAiYWJzZW50IiAicHJlc2VudCIgZnJvbSBvcmlnaW5hbCBkYXRhCiAgI2FibmRhdGEgPC0gYXMuZGF0YS5mcmFtZShhYm5kYXRhKQoKICBkdC5ib290WywxOjVdIDwtIGFzLmRhdGEuZnJhbWUobGFwcGx5KGR0LmJvb3RbLDE6NV0sIGZhY3RvcikpIAogIAogIAojIEJ1aWxkIGEgY2FjaGUgb2YgYWxsIGxvY2FsIGNvbXB1dGF0aW9ucwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogbXljYWNoZSA8LSBidWlsZFNjb3JlQ2FjaGUoZGF0YS5kZiA9IGR0LmJvb3QsIGRhdGEuZGlzdHMgPSBkaXN0LCBkYWcuYmFubmVkID0gYmFubmVkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhZy5yZXRhaW5lZCA9IHJldGFpbiwgbWF4LnBhcmVudHMgPSBtYXgucGFyKQoKIyBSdW4gdGhlIEVYQUNUIFNFQVJDSAojIC0tLS0tLS0tLS0tLS0tLS0tLS0tCiBtcC5kYWcgPC0gbW9zdFByb2JhYmxlKHNjb3JlLmNhY2hlID0gbXljYWNoZSkKIGZhYm4gPC0gZml0QWJuKG9iamVjdCA9IG1wLmRhZykKCiMgU2F2ZSB0aGUgcmVzdWx0cyBvYnRhaW5lZAojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIAogI2Jvb3Quc2F2ZVtbaV1dIDwtIGxpc3QobXAuZGFnLCBmYWJuLCBkdC5ib290KSAKIAogYm9vdC5zYXZlW1tpXV0gPC0gbXAuZGFnJGRhZwoKfQoKc2F2ZShib290LnNhdmUsIGZpbGUgPSBzcHJpbnRmKCdCb290RGF0YS9kdC5ib290LlJEYXRhJykpCgpgYGAKCiZuYnNwOwoKIyMgRmluZCB0aGUgZmluYWwgcHJ1bmVkIERBRwoKRmlyc3Qgd2UgbmVlZCB0byBsb2FkIGFsbCB0aGUgYm9vdHN0cmFwIERBR3MuCgpgYGB7ciBsb2FkQm9vdCwgZWNobz1UUlVFLCBldmFsPVRSVUV9CgojIHNldCBuciBib290c3RyYXAgc2FtcGxlcwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpuIDwtIDEwMDAKCiMgbG9hZCBkYWdzIGFuZCBib29zdHJhcCBkYXRhCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmRhZ3MgPC0gbGlzdCgpCmJvb3QgPC0gbGlzdCgpCgpsb2FkKGZpbGUgPSAnQm9vdERhdGEvZHQuYm9vdC5SRGF0YScpCgpmb3IoaSBpbiAxOm4pewpkYWdzW1tpXV0gPC0gYm9vdC5zYXZlW1tpXV0KCn0KCgpgYGAKCgombmJzcDsKClRoZW4gd2Ugd2lsbCBjaGVjayB3aGF0IHdhcyB0aGUgbW9zdCBmcmVxdWVudCBudW1iZXIgb2YgYXJjczoKCmBgYHtyIGNoZWNrQm9vdCwgZWNobz1UUlVFfQoKIyBjb3VudCB0b3RhbCBudW1iZXIgb2YgYXJjcyBpbiBlYWNoIGRhZwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmFyY3MgPC0gc2FwcGx5KGRhZ3MsIHN1bSkKYmFycGxvdCh0YWJsZShhcmNzKSkKCmBgYAoKRm9yIGNvbXBhcmlzb24sIGluIHRoZSBvcmlnaW5hbCBEQUcgdGhlcmUgd2VyZSBgciBzdW0obXlkYWckZGFnKWAgYXJjcywgc28gaXQgc2VlbXMgdGhhdCB0aGVyZSBtaWdodCBoYXZlIGJlZW4gc29tZSBvdmVyZml0dGluZy4KCiZuYnNwOwoKRmluYWxseSwgd2Ugd2lsbCBjb3VudCBob3cgbWFueSB0aW1lcyAqKmVhY2ggYXJjKiogYXBwZWFycyBpbiB0aGUgYm9vdHN0cmFwcGVkIGRhdGEuIFRoZSBmaW5hbCAqKnBydW5lZCBEQUcqKiB3aWxsIGluY2x1ZGUgb25seSBhcmNzIHByZXNlbnQgaW4gYXQgbGVhc3QgNTAlIG9mIGJvb3RzdHJhcCBzYW1wbGVzIChlLmcuIDUwMC8xMDAwIGluIG91ciBjYXNlKS4KCgpgYGB7ciBwcnVuZWQsIGVjaG89VFJVRX0KCiMgQ291bnQgaG93IG1hbnkgdGltZXMgZWFjaCBhcmMgYXBwZWFyIGluIHRoZSBib290c3RyYXAgZGF0YQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBmdW5jdGlvbiBSZWR1Y2Ugc3VtcyAoIisiKSBlYWNoIGVsZW1lbnQgb2YgZWFjaCBtYXRyaXggaW4gdGhlIGxpc3QgYW5kIHN0b3JlIAojIHJlc3VsdHMgaW4gYSBuZXcgbWF0cml4IG9mIHNhbWUgc2l6ZQoKYWxsZGFnIDwtIFJlZHVjZSgiKyIsIGRhZ3MpICAKCiMgZXhwcmVzcyBpdCBpbiBwZXJjZW50YWdlCnBlcmRhZyA8LSBhbGxkYWcvbGVuZ3RoKGRhZ3MpCgoKIyBLZWVwIG9ubHkgYXJjcyB0aGF0IGFwcGVhcnMgaW4gYXQgbGVhc3QgNTAlIG9mIHNhbXBsZXMKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KdHJpbS5kYWcgPC0gKGFsbGRhZyA+PShuKjAuNSkpKjEKCiMgU2VuZCAgZmluYWwgcHJ1bmVkIERBRyB0byBHcmFwaHZpcyBmb3IgdmlzdWFsaXphdGlvbiAKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQp0b0dyYXBodml6KGRhZyA9IHRyaW0uZGFnLCBkYXRhLmRmID0gYWJuZGF0YSwgZGF0YS5kaXN0cyA9IGRpc3QsCiAgICAgICAgICAgb3V0ZmlsZSA9IHBhc3RlMCgiVHJpbURBRyIsbiwiLmRvdCIpKQoKYGBgCgogIApUaGlzIGlzIHRoZSBwZXJjZW50YWdlIG9mIGFyY3MgcmV0cmlldmVkIHdpdGhpbiB0aGUgYm9vdHN0cmFwIHNhbXBsZToKCmBgYHtyIHBlcmMsIGVjaG89RkFMU0V9Cgpyb3VuZChwZXJkYWcqMTAwLDApCgpgYGAKCgpUaGlzIGlzIGhvdyB0aGUgZmluYWwgKipwcnVuZWQgREFHKiogbG9va3MgbGlrZToKCmBgYHtyIHByREFHc2hvd30KCnBsb3RBYm4oZGFnID0gdHJpbS5kYWcsIGRhdGEuZGlzdHMgPSBkaXN0KQoKYGBgCgombmJzcDsKCk5vdyB0aGF0IHdlIGhhdmUgYSByb2J1c3QgbW9kZWwgKGVuY29kZWQgYnkgdGhlIHBydW5lZCBEQUcpLCB3ZSBjYW4gZXh0cmFjdCB0aGUgcGFyYW1ldGVycyB0byAoYSkgYXBwcmVjaWF0ZSB0aGUgbWFnbml0dWRlIChhbmQgcHJlY2lzaW9uKSBvZiB0aGUgdGhlIGFzc29jaWF0aW9ucyBhbmQgKGIpIGZ1cnRoZXIgcmVmaW5lIHRoZSBEQUcgaW5jbHVkaW5nIHdoZXRoZXIgdGhlIGFzc29jaWF0aW9ucyBhcmUgcG9zaXRpdmUgb3IgbmVnYXRpdmUuIAoKJm5ic3A7CgojIEV4dHJhY3QgbWFyZ2luYWwgcG9zdGVyaW9yIGRlbnNpdHkgZm9yIGVhY2ggcGFyYW1ldGVyCgpUaGUgbWFyZ2luYWwgcG9zdGVyaW9yIGRlbnNpdGllcyAobWFyZ2luYWxzKSByZXByZXNlbnQgdGhlIGRlbnNpdHkgZGlzdHJpYnV0aW9uIG9mIHRoZSBwYXJhbWV0ZXJzIGF0IGVhY2ggYXJjLgoKYGBge3IgbXBkLCBlY2hvPVRSVUV9CgptYXJnLmYgPC0gZml0QWJuKGRhZyA9IHRyaW0uZGFnLCBkYXRhLmRmID0gYXMuZGF0YS5mcmFtZShhYm5kYXRhKSwKICAgICAgICAgICAgICAgICBkYXRhLmRpc3RzID0gZGlzdCwgY29tcHV0ZS5maXhlZD1UUlVFLCBuLmdyaWQ9MTAwMCkKCmBgYAoKJm5ic3A7CgojIyBWaXN1YWxseSBpbnNwZWN0IHRoZSBtYXJnaW5hbCBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9ucyBvZiB0aGUgcGFyYW1ldGVycwpgYGB7ciBtcGRfcGxvdCwgZWNobz1UUlVFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9Mn0KCnBhcihtZnJvdz1jKDEsNCksIG1hcj1jKDIsMiwxLjUsMSkpCmZvcihpIGluIDE6bGVuZ3RoKG1hcmcuZiRtYXJnaW5hbHMpKXsKCiMgZ2V0IHRoZSBtYXJnaW5hbCBmb3IgY3VycmVudCBub2RlLCB3aGljaCBpcyBhIG1hdHJpeCBbeCwgZih4KV0KICBjdXIubm9kZSA8LSBtYXJnLmYkbWFyZ2luYWxzW2ldCiAgbm9tMSA8LSBuYW1lcyhtYXJnLmYkbWFyZ2luYWxzKVtpXQoKIyBwaWNrIHRoZSBmaXJzdCB2YWx1ZSAoZm9yIG1vZGVscyB3b3Rob3V0IHJhbmRvbSBlZmZlY3RzKQogIGN1ci5ub2RlIDwtIGN1ci5ub2RlW1sxXV0KICBmb3IoaiBpbiAxOmxlbmd0aChjdXIubm9kZSkgKSB7CiAgICBub20yPC1uYW1lcyhjdXIubm9kZSlbal0KICAgIGN1ci5wYXJhbSA8LSBjdXIubm9kZVtbal1dCiAgICBwbG90KGN1ci5wYXJhbSx0eXBlPSJsIixtYWluPXBhc3RlKG5vbTEsICI6Iiwgbm9tMiksIGNleD0wLjcpCiAgfQp9CgpgYGAKCgombmJzcDsKCiMgR2V0IHRoZSB0YWJsZSBvZiBxdWFudGlsZXMgZm9yIHRoZSBtYXJnaW5hbHMKYGBge3IgbXBkX3RhYiwgZWNobz1UUlVFfQoKIyBleHRyYWN0IG1hcmdpbmFscyBhZGp1c3RlZCBmb3IgZ3JvdXBlZCBkYXRhCm1hcmcuZGlzdHMgPC0gbWFyZy5mJG1hcmdpbmFsc1tbMV1dCmZvciAoaSBpbiAyOmxlbmd0aChtYXJnLmYkbWFyZ2luYWxzKSkgewogIG1hcmcuZGlzdHMgPC0gYyhtYXJnLmRpc3RzLCBtYXJnLmYkbWFyZ2luYWxzW1tpXV0pCn0KCm1hdCA8LSBtYXRyaXgocmVwKE5BLCBsZW5ndGgobWFyZy5kaXN0cykqMyksIG5jb2w9MykKcm93bmFtZXMobWF0KSA8LSBuYW1lcyhtYXJnLmRpc3RzKQpjb2xuYW1lcyhtYXQpIDwtIGMoIjIuNSUiLCAiNTAlIiwgIjk3LjUlIikKaWdub3JlLm1lIDwtIHVuaW9uKGdyZXAoIlxcKEludCIsIG5hbWVzKG1hcmcuZGlzdHMpKSwgZ3JlcCgicHJlYyIsIG5hbWVzKG1hcmcuZGlzdHMpKSkgIyB0YWtlIGF3YXkgYmFja2dyb3VuZCBrIGFuZCBwcmVjaXNpb24KY29tbWVudCA8LSByZXAoIiIsIGxlbmd0aChtYXJnLmRpc3RzKSkKZm9yIChpIGluIDE6bGVuZ3RoKG1hcmcuZGlzdHMpKSB7CiAgdG1wIDwtIG1hcmcuZGlzdHNbW2ldXQogIHRtcDIgPC0gY3Vtc3VtKHRtcFssMl0pL3N1bSh0bXBbLDJdKQogIG1hdFtpLCBdIDwtYyh0bXBbd2hpY2godG1wMj4wLjAyNSlbMV0sMV0sIyMgLTEgaXMgc28gdXNlIHZhbHVlIG9uIHRoZSBsZWZ0IG9mIHRoZSAyLjUlCiAgICAgICAgICAgICAgIHRtcFt3aGljaCh0bXAyPjAuNSlbMV0sMV0sCiAgICAgICAgICAgICAgIHRtcFt3aGljaCh0bXAyPjAuOTc1KVsxXSwxXSkKICB2ZWMgPC0gbWF0W2ksXQoKICBpZiggIShpJWluJWlnbm9yZS5tZSkgJiYgICh2ZWNbMV08MCAmJiB2ZWNbM10+MCkpe2NvbW1lbnRbaV08LSJub3Qgc2lnLiBhdCA1JSJ9CgogICMjIHRydW5jYXRlIGZvciBwcmludGluZwogIG1hdFtpLF08LWFzLm51bWVyaWMoZm9ybWF0QyhtYXRbaSxdLGRpZ2l0cz0zLGZvcm1hdD0iZiIpKQp9Cgprbml0cjo6a2FibGUoY2JpbmQobWF0KSwgcm93Lm5hbWVzID0gVFJVRSwgZGlnaXRzID0gMywgYWxpZ24gPSAicnJyciIsICJodG1sIikgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEYsIHBvc2l0aW9uID0gImxlZnQiKSAlPiUKICBjb2x1bW5fc3BlYygzLCBib2xkID0gVFJVRSkKCgpgYGAKCkFzIHNhaWQsIHRoZSBtYXJnaW5hbHMgcmVwcmVzZW50IGVzdGltYXRlcyBvZiB0aGUgcGFyYW1ldGVycyBhdCBlYWNoIG5vZGUgKGkuZS4gdGhlIGFyY3MgaW4gdGhlIERBRykuIEJlaW5nIHRoZSB2YXJpYWJsZXMgb2YgZGlmZmVyZW50IG5hdHVyZSwgdGhleSBoYXZlIGRpZmZlcmVudCBtZWFuaW5nLiBNYXJnaW5hbHMgcmVwcmVzZW50ICoqY29ycmVsYXRpb24gY29lZmZpY2llbnRzKiogZm9yIEdhdXNzaWFuIG5vZGVzICh3aGVuIHRoZSBkZWZhdWx0ICpjZW50ZXJpbmcqIG9mIHZhcmlhYmxlcyBpcyBrZXB0KSwgKipsb2cgcmF0ZSByYXRpb3MqKiBmb3IgUG9pc3NvbiBub2RlcywgYW5kICoqbG9nIG9kZHMgcmF0aW9zKiogZm9yIGJpbm9taWFsIG5vZGVzLiBUaGVyZWZvcmUsIHRoZSBzZWNvbmQgYW5kIHRoZSBsYXR0ZXIgbmVlZCB0byBiZSBleHBvbmVudGlhdGVkLCB0byBnZXQgdGhlIG9kZHMgcmF0aW9zIG9yIHJhdGUgcmF0aW9zIHJlc3BlY3RpdmVseS4KCmBgYHtyIHBhcmFtZXRlcnMsIGVjaG89RkFMU0UsIGNhY2hlPUZBTFNFfQptYXI8LSBkYXRhLmZyYW1lKGNiaW5kKG1hdCkpCm5hbWVzKG1hcikgPC0gYygiMi41USIsICJtZWRpYW4iLCAiOTcuNVEiKQoKbWFyPC0gbWFyWy1pZ25vcmUubWUsXQptYXJbMTozLF08LWV4cChtYXJbMTozLF0pCm1hciRJbnRlcnByZXRhdGlvbiA8LSBjKHJlcCgib2RkcyByYXRpbyIsMyksCiAgICAgICAgICAgICAgICAgICAgICAgIHJlcCgicmF0ZSByYXRpbyIsIDQpLAogICAgICAgICAgICAgICAgICAgICAgICByZXAoImNvcnJlbGF0aW9uIiwgMikpCgoKa25pdHI6OmthYmxlKG1hciwgcm93Lm5hbWVzID0gVFJVRSwgZGlnaXRzID0gMywgYWxpZ24gPSAicnJyIiwgImh0bWwiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gRkFMU0UsIHBvc2l0aW9uID0gImxlZnQiKQoKYGBgCgombmJzcDsKCiMgUHJlc2VudCBmaW5hbCByZXN1bHRzCkFzIGEgZmluYWwgc3RlcCB3ZSB3aWxsIHJlZmluZSB0aGUgREFHIGJ5IChhKSBjaGFuZ2luZyB0aGUgc3R5bGUgb2YgdGhlIGFycm93cyBhY2NvcmRpbmcgdG8gdGhlIHR5cGUgb2YgYXNzb2NpYXRpb24gKGkuZS4gKnNvbGlkKiBhcnJvd3MgZm9yIHBvc2l0aXZlIGFzc29jaWF0aW9uIGFuZCAqZGFzaGVkKiBhcnJvd3MgZm9yIG5lZ2F0aXZlIGFzc29jaWF0aW9uKSwgYW5kIChiKSBjaGFuZ2luZyB0aGUgdGhpY2tuZXNzIG9mIHRoZSBhcnJvd3MgYWNjb3JkaW5nIHRvIHRoZWlyIGxpbmsgc3RyZW5ndGguIFRoZXNlIHN0ZXBzIGFyZSBkb25lIGluIEdyYXBodml6LCB1c2luZyB0aGUgKipET1QgbGFuZ3VhZ2UqKi4gRGV0YWlscyBjYW4gYmUgZm91bmQgYXQKaHR0cHM6Ly93d3cuZ3JhcGh2aXoub3JnL2RvYy9pbmZvL2F0dHJzLmh0bWwuCgombmJzcDsKCjxzdHlsZT4KICAuY29sMiB7CiAgICBjb2x1bW5zOiAyIDIwMHB4OyAgICAgICAgIC8qIG51bWJlciBvZiBjb2x1bW5zIGFuZCB3aWR0aCBpbiBwaXhlbHMqLwogICAgLXdlYmtpdC1jb2x1bW5zOiAyIDIwMHB4OyAvKiBjaHJvbWUsIHNhZmFyaSAqLwogICAgLW1vei1jb2x1bW5zOiAyIDIwMHB4OyAgICAvKiBmaXJlZm94ICovCiAgfQo8L3N0eWxlPgoKPGRpdiBjbGFzcz0iY29sMiI+CgpgYGB7ciBmaW5hbCwgZWNobz1GQUxTRSwgY2FjaGU9RkFMU0V9CgppbmNsdWRlX2dyYXBoaWNzKCJmaW5hbC1ncmFwaC5wbmciKQoKa25pdHI6OmthYmxlKG1hciwgcm93Lm5hbWVzID0gVFJVRSwgZGlnaXRzID0gMywgYWxpZ24gPSAicnJyciIsICJodG1sIikgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEZBTFNFLCBwb3NpdGlvbiA9ICJsZWZ0IikgJT4lCiAgY29sdW1uX3NwZWMoMywgYm9sZCA9IFRSVUUpCgoKYGBgCjwvZGl2PgoKRm9yIGFuIGV4YW1wbGUgaW4gYSByZWNlbnQgcGVlciByZXZpZXdlZCBwdWJsaWNhdGlvbiBzZWUgW0Bjb21pbjIwMTlyZXZlYWxpbmddIG9yIFtAa3JhdHplcjIwMjBiYXllc2lhbl0KCiMgUmVmZXJlbmNlcwoK