Below is the hands-on exercises on the advanced methods. This will cover the heuristic search, how to handle clustering and how to perform Bayesian model averaging.
Note: in the following text: Bayesian network, structure and DAG are synonyms.
Mixed models – correction for grouped data (or clustering)
Note: In the following: clustering and grouped are taken as synonym.
In some situations, the way the data were collected has a clear grouping aspect, and therefore there is a potential risk for non-independence between data points from the same group that could cause over-dispersion. This can lead to analyses which are over-optimistic as the true level of variation in the data is under-estimated. It could have an impact … or not. But this is a good practice to check!
In practice we will introduce a random effect to account for this additional variability. Thus each node will become a GLMM (Generalized Linear Mixed Model (Faraway, 2016)) instead of a GLM (Generalized Linear Model (McCullagh, 2018)) but in a Bayesian setting. We will then compute the posterior distribution and check if they widen. In such case, we will have to take into account the clustering in the scoring scheme we use.
In practice, the major problem is the computational complexity of this approach. Indeed, if the clustering do not affect too much the result, it is preferable to not take it into account. The model is then much simpler and parsimonious then more generalizable.
The grouping variable is farm
and we apply the random effect to every nodes. On a personal computer following code takes 15 minutes where the previous code ran in less than a second! The grouping variable has 15 levels.
marg.f.grouped <- fitabn(dag.m = trim.dag,
data.df = as.data.frame(abndata),
data.dists = dist,
group.var = "farm",
cor.vars = c("AR", "pneumS", "female", "livdam", "eggs", "wormCount", "age", "adg"),
compute.fixed = TRUE,
n.grid = 1000)
Calculate the area under the density curve
|
auc
|
AR|(Intercept)
|
0.9999986
|
AR|age
|
0.9999994
|
AR|group.precision
|
0.9999853
|
pneumS|(Intercept)
|
0.9999977
|
pneumS|group.precision
|
0.9999532
|
female|(Intercept)
|
0.9999996
|
female|group.precision
|
0.9992713
|
livdam|(Intercept)
|
0.9999978
|
livdam|eggs
|
0.9999976
|
livdam|group.precision
|
0.9999974
|
eggs|(Intercept)
|
0.9999957
|
eggs|adg
|
0.9999990
|
eggs|group.precision
|
0.9999999
|
wormCount|(Intercept)
|
0.9999972
|
wormCount|AR
|
0.9999995
|
wormCount|eggs
|
0.9999991
|
wormCount|age
|
0.9999997
|
wormCount|adg
|
0.9999997
|
wormCount|group.precision
|
0.9999976
|
age|(Intercept)
|
0.9999984
|
age|female
|
0.9999996
|
age|group.precision
|
0.9999982
|
age|precision
|
0.9999995
|
adg|(Intercept)
|
0.9999993
|
adg|age
|
0.9999997
|
adg|group.precision
|
0.9999725
|
adg|precision
|
0.9999980
|
Here the shape of some of the parameter distribution looks weird. Indeed, there is a deal of information between the random strata. However, the AUC looks good. So we can proceed getting the estimates.
Get the table of quantiles for the marginals
As one can see, except for a precision parameter (female|group.precision
) the quantiles seems to not have too much widen.
Non-adjusted marginal densities
|
2.5%
|
50%
|
97.5%
|
AR|(Intercept)
|
0.891
|
1.155
|
1.435
|
AR|age
|
0.509
|
0.798
|
1.110
|
pneumS|(Intercept)
|
-2.134
|
-1.814
|
-1.519
|
female|(Intercept)
|
-0.196
|
0.018
|
0.230
|
livdam|(Intercept)
|
0.976
|
1.265
|
1.567
|
livdam|eggs
|
0.663
|
1.547
|
2.656
|
eggs|(Intercept)
|
-1.508
|
-1.232
|
-0.971
|
eggs|adg
|
0.346
|
0.614
|
0.895
|
wormCount|(Intercept)
|
-1.646
|
-1.400
|
-1.170
|
wormCount|AR
|
0.152
|
0.273
|
0.395
|
wormCount|eggs
|
3.280
|
3.504
|
3.741
|
wormCount|age
|
-0.824
|
-0.685
|
-0.548
|
wormCount|adg
|
-0.374
|
-0.240
|
-0.107
|
age|(Intercept)
|
-0.349
|
-0.200
|
-0.052
|
age|female
|
0.188
|
0.397
|
0.606
|
age|precision
|
0.893
|
1.042
|
1.207
|
adg|(Intercept)
|
-0.053
|
0.000
|
0.053
|
adg|age
|
-0.921
|
-0.868
|
-0.816
|
adg|precision
|
3.485
|
4.070
|
4.712
|
Marginal densities adjusted with random effects
|
2.5%
|
50%
|
97.5%
|
AR|(Intercept)
|
0.761
|
1.236
|
1.766
|
AR|age
|
0.371
|
0.758
|
1.163
|
AR|group.precision
|
0.633
|
2.105
|
10.084
|
pneumS|(Intercept)
|
-2.664
|
-2.006
|
-1.521
|
pneumS|group.precision
|
0.533
|
2.339
|
12.875
|
female|(Intercept)
|
-0.209
|
0.025
|
0.256
|
female|group.precision
|
166.132
|
2333.598
|
4707.719
|
livdam|(Intercept)
|
1.067
|
1.826
|
2.748
|
livdam|eggs
|
-0.116
|
1.040
|
2.346
|
livdam|group.precision
|
0.229
|
0.666
|
1.762
|
eggs|(Intercept)
|
-3.071
|
-1.687
|
-0.413
|
eggs|adg
|
-0.344
|
0.100
|
0.544
|
eggs|group.precision
|
0.071
|
0.207
|
0.515
|
wormCount|(Intercept)
|
-2.562
|
-1.756
|
-1.043
|
wormCount|AR
|
0.325
|
0.452
|
0.579
|
wormCount|eggs
|
2.428
|
2.700
|
2.980
|
wormCount|age
|
-0.266
|
-0.093
|
0.080
|
wormCount|adg
|
-0.244
|
-0.100
|
0.044
|
wormCount|group.precision
|
0.240
|
0.631
|
1.425
|
age|(Intercept)
|
-0.629
|
-0.221
|
0.184
|
age|female
|
0.312
|
0.446
|
0.579
|
age|group.precision
|
0.816
|
1.854
|
3.580
|
age|precision
|
2.214
|
2.594
|
3.012
|
adg|(Intercept)
|
-0.162
|
-0.017
|
0.125
|
adg|age
|
-0.904
|
-0.838
|
-0.772
|
adg|group.precision
|
6.610
|
15.856
|
33.294
|
adg|precision
|
4.706
|
5.514
|
6.402
|
Heuristic search
The mostprobable()
function is limited to 20 to 25 nodes (if computed on a computing cluster even with a limited number of parent). Then for larger problem this approach becomes intractable form a computational point of view. The main advantages of the mostprobable()
function is that the returned structure is the one that has the maximal possible score, thus it is called exact in that sense (Koivisto & Sood, 2004). It compares all possible structure to select the optimal one.
The heuristic searches are techniques that tends to perform a greedy optimization. Then there is no guarantees to reach the maximum score. In this context greedy means: perform local optimization and hoping to get the overall maximum.
We will use a Hill-Climber (Korb & Nicholson, 2010). We need to set up the number of searches and the number of steps per searches. The starting point (here we choose a random Directed Acyclic Graph (DAG)). The rest of parameters is equivalent to the ones used in buildscorecache()
function.
abndata <- dt[, -drop]
abndata[,1:5] <- as.data.frame(lapply(abndata[,1:5], factor))
dist <- list(AR = "binomial", pneumS = "binomial", female="binomial",
livdam= "binomial", eggs = "binomial",wormCount = "poisson",
age= "gaussian", adg = "gaussian")
#set maximum number of possible parent per node
max.par <- 4
# compute a cache of scores
mycache <- buildscorecache(data.df = as.data.frame(abndata),
data.dists = dist,
max.parents = max.par)
# set number of searches and number of steps
num.searches <- 200
max.steps <- 150
# Hill-climber
heur.res <- quiet(search.heuristic(score.cache = mycache,
score = "mlik",
data.dists = dist,
max.parents = 4,
start.dag = "random",
num.searches = num.searches,
max.steps = max.steps,
seed = 3213,
verbose = TRUE,
algo = "hc"))
# for comparison let us compute the maximum exact score
mydag <- quiet(mostprobable(score.cache = mycache))
fabn <- fitabn(dag.m = mydag, data.df = as.data.frame(abndata), data.dists = dist)
# plot Hill-Climber scores
df.heur <- unlist(heur.res$scores)
plot(NULL,lty=1, xlab="Index of heuristic search",ylab="BN score", ylim = c(max(unlist(df.heur)),min(unlist(df.heur))), xlim = c(0,num.searches))
for(i in 1:num.searches){
if(sum(i==order(df.heur,decreasing = TRUE)[1:10])){
points(x=i,y=df.heur[i],type="p",pch=19, col=rgb(0,0,1, 0.8),lwd = 2)
}else{
points(x=i,y=df.heur[i],type="p",pch=19, col=rgb(0,0,0, 0.3))
}
}
points(x = max(unlist(df.heur)),y = max(unlist(df.heur)),col="red",pch=19)
abline(h = fabn$mlik, col="red",lty = 3)
Above we plot the maximum scores after 150 steps out of 200 different searches of a Hill-Climber. The blues dotes are the 10 best searches. The red dashed line is the maximum possible score.
# let us plot the evolution of scores during optimization
Long <- (heur.res$detailed.score)
Long.arr <- array(unlist(Long), dim = c(nrow(Long[[1]]), ncol(Long[[1]]), length(Long)))
plot(NULL,lty=1, xlab="Number of Steps",ylab="BN score", ylim = c(max(unlist(df.heur)),min(unlist(df.heur))), xlim = c(0,max.steps))
for(i in 1:num.searches){
if(sum(i==order(unlist(heur.res$scores),decreasing = TRUE)[1:10])){
points(x=1:(max.steps-1),y=Long.arr[1,,i],type="l",lty=1, col=rgb(0,0,1, 0.8),lwd = 2)
}else{
points(x=1:(max.steps-1),y=Long.arr[1,,i],type="l",lty=1, col=rgb(0,0,0, 0.25))
}
}
lines(x=1:(max.steps-1),y=Long.arr[1,,which.max(unlist(heur.res$scores))],type="l",col="red",lwd=3)
abline(h = fabn$mlik, col="red",lty = 3)
Above we plot the scores in function of the steps for the 200 searches. The blue lines are the 10 best and the red one is the one that has the highest score. As one can see, in searches that work well the optimization is already good after 60 steps. This plot is a diagnostic plot used to set up the number of required steps.
MCMC over the structures
Usually, the output of a Bayesian network analysis of a dataset ends-up with a single well adjusted DAG. From the researcher point of view this could be frustrating. Indeed, the model is the one that is best supported by the data but the uncertainty quantification is missing. Classically in epidemiology, researchers are used to express point estimate with an uncertainty measure. An arc in a Bayesian network is a point estimate, we will see how to perform model averaging. The link strength measure is designed to account for that. An more natural alternative is to perform MCMC over structures (Friedman & Koller, 2003).
We use the mcmcabn()
function on the cache of pre-computed networks scores. One needs to define the type of score used (here is the marginal likelihood mlik
). The maximum of number of parents per node (same as the one used in buildscorecache()
). The MCMC learning scheme, defined as: number of MCMC samples, number of thinned sampled (to avoid autocorrelation) and the length of the burn-in phase. Possibly a starting DAG and a structural prior. We also need to select the relative probability of performing radical moves (shuffling). Indeed, a naive MCMC approach is known to get very easily stuck in local maximum (for more details see: (Grzegorczyk & Husmeier, 2008; Su & Borsuk, 2016)).
mcmc.out <- mcmcabn(score.cache = mycache,
score = "mlik",
data.dists = dist,
max.parents = 4,
mcmc.scheme = c(1000,9,500),
seed = 321,
verbose = FALSE,
start.dag = "random",
prob.rev = 0.07,
prob.mbr = 0.07,
prior.choice = 1)
This is again computationally complex!
Her is a plot of the MCMC samples. One can see the scores of the structures on the y-axis in function of the index steps. The dots are the radical moves. One the right side a histogram shows the number of structures with a given score. As one can see the histogram is very peaked on the maximum possible score.
One can also display the cumulative maximum score (used for network score optimization).
But the major advantage of this method is the possibility of querying the MCMC sample using a formula statement.
# average individual arc support
query(mcmcabn = mcmc.out)
AR pneumS female livdam eggs
AR 0.00000000 0.01069893 0.01139886 0.01159884 0.07599240
pneumS 0.00589941 0.00000000 0.01539846 0.00829917 0.10398960
female 0.00579942 0.01879812 0.00000000 0.01769823 0.00659934
livdam 0.00689931 0.00999900 0.01119888 0.00000000 0.44275572
eggs 0.14908509 0.09239076 0.01019898 0.29377062 0.00000000
wormCount 0.76062394 0.08119188 0.03349665 0.07039296 0.91700830
age 0.13568643 0.07349265 0.15768423 0.11338866 0.09739026
adg 0.03019698 0.01589841 0.08359164 0.00959904 0.17288271
wormCount age adg
AR 0.00349965 0.77212279 0.08649135
pneumS 0.00429957 0.19238076 0.03989601
female 0.00249975 0.29857014 0.12168783
livdam 0.00439956 0.06649335 0.01169883
eggs 0.02959704 0.25057494 0.36466353
wormCount 0.00000000 0.88481152 0.31056894
age 0.01399860 0.00000000 0.47095290
adg 0.00349965 0.52864714 0.00000000
# probability that worm count being linked to age but not to female directly
query(mcmcabn = mcmc.out,formula = ~wormCount|age-wormCount|female)
[1] 0.01889811
# probability that worm count being directly linked to age and adg and that adg is link to age (undirected)
query(mcmcabn = mcmc.out,formula = ~wormCount|age + wormCount|adg + age|adg)+
query(mcmcabn = mcmc.out,formula = ~wormCount|age + wormCount|adg + adg|age)
[1] 0.2451755
References
Faraway, J. J. (2016). Extending the linear model with r: Generalized linear, mixed effects and nonparametric regression models. Chapman; Hall/CRC.
Friedman, N., & Koller, D. (2003). Being bayesian about network structure. a bayesian approach to structure discovery in bayesian networks. Machine Learning, 50(1-2), 95–125.
Grzegorczyk, M., & Husmeier, D. (2008). Improving the structure mcmc sampler for bayesian networks by introducing a new edge reversal move. Machine Learning, 71(2-3), 265.
Koivisto, M., & Sood, K. (2004). Exact bayesian structure discovery in bayesian networks. Journal of Machine Learning Research, 5(May), 549–573.
Korb, K. B., & Nicholson, A. E. (2010). Bayesian artificial intelligence. CRC press.
McCullagh, P. (2018). Generalized linear models. Routledge.
Su, C., & Borsuk, M. E. (2016). Improving structure mcmc for bayesian networks through markov blanket resampling. The Journal of Machine Learning Research, 17(1), 4042–4061.
LS0tCnRpdGxlOiAiSGFuZHMtb24gZXhlcmNpc2U6IGFkdmFuY2VkIG1ldGhvZHMiCmZvbnRzaXplOiAxMnB0Cm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDIKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKYmlibGlvZ3JhcGh5OiBiaWJfYWR2YW5jZWQuYmliCmNzbDogYXBhLmNzbAotLS0KCiZuYnNwOwoKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IEZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjb2xsYXBzZT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9MTAsIGNvbW1lbnQgPSBOQSkKCm9wdGlvbnMoc2NpcGVuPTk5OSkKCnF1aWV0IDwtIGZ1bmN0aW9uKHgpIHsgCiAgc2luayh0ZW1wZmlsZSgpKSAKICBvbi5leGl0KHNpbmsoKSkgCiAgaW52aXNpYmxlKGZvcmNlKHgpKSAKfSAKCiMjIGxvYWQgZGF0YSwgbGlzdCBvZiBkaXN0IGV0YwpkdCA8LSByZWFkUkRTKCJkYXRhT0suUkRTIikKCmRyb3AgPC0gd2hpY2goY29sbmFtZXMoZHQpJWluJSBjKCJwbmV1bSIsICJlcGc1IiwgIndvcm1zIiwgImZhcm0iKSkKZHJvcC5jbHVzdHJpbmcgPC0gd2hpY2goY29sbmFtZXMoZHQpJWluJSBjKCJwbmV1bSIsICJlcGc1IiwgIndvcm1zIikpCgojI2VuZCBvZiBvbGQgc3R1ZmYKCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeShhYm4pCmxpYnJhcnkobWNtY2FibikKYGBgCgpCZWxvdyBpcyB0aGUgaGFuZHMtb24gZXhlcmNpc2VzIG9uIHRoZSBhZHZhbmNlZCBtZXRob2RzLiBUaGlzIHdpbGwgY292ZXIgdGhlICoqaGV1cmlzdGljIHNlYXJjaCoqLCBob3cgdG8gaGFuZGxlICoqY2x1c3RlcmluZyoqIGFuZCBob3cgdG8gcGVyZm9ybSAqKkJheWVzaWFuIG1vZGVsIGF2ZXJhZ2luZyoqLgoKKk5vdGU6IGluIHRoZSBmb2xsb3dpbmcgdGV4dDogQmF5ZXNpYW4gbmV0d29yaywgc3RydWN0dXJlIGFuZCBEQUcgYXJlIHN5bm9ueW1zLioKCiMgTWl4ZWQgbW9kZWxzIOKAkyBjb3JyZWN0aW9uIGZvciBncm91cGVkIGRhdGEgKG9yIGNsdXN0ZXJpbmcpCgoqTm90ZTogSW4gdGhlIGZvbGxvd2luZzogY2x1c3RlcmluZyBhbmQgZ3JvdXBlZCBhcmUgdGFrZW4gYXMgc3lub255bS4qCgpJbiBzb21lIHNpdHVhdGlvbnMsIHRoZSB3YXkgdGhlIGRhdGEgd2VyZSBjb2xsZWN0ZWQgaGFzIGEgY2xlYXIgZ3JvdXBpbmcgYXNwZWN0LCBhbmQgdGhlcmVmb3JlIHRoZXJlIGlzIGEgcG90ZW50aWFsIHJpc2sgZm9yIG5vbi1pbmRlcGVuZGVuY2UgYmV0d2VlbiBkYXRhIHBvaW50cyBmcm9tIHRoZSBzYW1lIGdyb3VwIHRoYXQgY291bGQgY2F1c2Ugb3Zlci1kaXNwZXJzaW9uLiBUaGlzIGNhbiBsZWFkIHRvIGFuYWx5c2VzIHdoaWNoIGFyZSBvdmVyLW9wdGltaXN0aWMgYXMgdGhlIHRydWUgbGV2ZWwgb2YgdmFyaWF0aW9uIGluIHRoZSBkYXRhIGlzIHVuZGVyLWVzdGltYXRlZC4gSXQgY291bGQgaGF2ZSBhbiBpbXBhY3QgLi4uIG9yIG5vdC4gQnV0IHRoaXMgaXMgYSBnb29kIHByYWN0aWNlIHRvIGNoZWNrIQoKSW4gcHJhY3RpY2Ugd2Ugd2lsbCBpbnRyb2R1Y2UgYSByYW5kb20gZWZmZWN0IHRvIGFjY291bnQgZm9yIHRoaXMgYWRkaXRpb25hbCB2YXJpYWJpbGl0eS4gVGh1cyBlYWNoIG5vZGUgd2lsbCBiZWNvbWUgYSAqKkdMTU0qKiAoR2VuZXJhbGl6ZWQgTGluZWFyIE1peGVkIE1vZGVsIFtAZmFyYXdheTIwMTZleHRlbmRpbmddKSBpbnN0ZWFkIG9mIGEgKipHTE0qKiAoR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVsIFtAbWNjdWxsYWdoMjAxOGdlbmVyYWxpemVkXSkgYnV0IGluIGEgQmF5ZXNpYW4gc2V0dGluZy4gV2Ugd2lsbCB0aGVuIGNvbXB1dGUgdGhlIHBvc3RlcmlvciBkaXN0cmlidXRpb24gYW5kIGNoZWNrIGlmIHRoZXkgKip3aWRlbioqLiBJbiBzdWNoIGNhc2UsIHdlIHdpbGwgaGF2ZSB0byB0YWtlIGludG8gYWNjb3VudCB0aGUgY2x1c3RlcmluZyBpbiB0aGUgc2NvcmluZyBzY2hlbWUgd2UgdXNlLgoKSW4gcHJhY3RpY2UsIHRoZSBtYWpvciBwcm9ibGVtIGlzIHRoZSBjb21wdXRhdGlvbmFsIGNvbXBsZXhpdHkgb2YgdGhpcyBhcHByb2FjaC4gSW5kZWVkLCBpZiB0aGUgY2x1c3RlcmluZyBkbyBub3QgYWZmZWN0IHRvbyBtdWNoIHRoZSByZXN1bHQsIGl0IGlzIHByZWZlcmFibGUgdG8gbm90IHRha2UgaXQgaW50byBhY2NvdW50LiBUaGUgbW9kZWwgaXMgdGhlbiBtdWNoIHNpbXBsZXIgYW5kIHBhcnNpbW9uaW91cyB0aGVuIG1vcmUgZ2VuZXJhbGl6YWJsZS4gIAoKYGBge3J9CiMgaW5jb3Jwb3JhdGUgZ3JvdXBpbmcgZmFjdG9yIGluIHRoZSBkYXRhLgphYm5kYXRhIDwtIGR0WywgLWRyb3AuY2x1c3RyaW5nXQoKIyBzZXQgdXAgZmFjdG9ycwphYm5kYXRhWyxjKDE6NSw5KV0gPC0gYXMuZGF0YS5mcmFtZShsYXBwbHkoYWJuZGF0YVssYygxOjUsOSldLCBmYWN0b3IpKQoKIyByZWNvbXB1dGUgdGhlIHRyaW1lZCBEQUcgKGkuZS4gYXJjIHN1cHBvcnRlZCBieSBhdCBsZWFzdCA1MCUgb2YgdGhlIGJvb3RzdHJhcCBzYW1wbGVzKQpkYWdzIDwtIHJlYWRSRFMoIkJvb3REQUdzNTAwMC5SRFMiKQpkYWdzIDwtIGFycmF5KGRhdGEgPSB1bmxpc3QoZGFncyksZGltID0gYyg4LDgsNTAwMCkpCmRhZyA8LSBhcHBseShkYWdzLDE6MiwgbWVhbikKCnRyaW0uZGFnIDwtIGRhZwp0cmltLmRhZ1tkYWc+MC41XSA8LSAxCnRyaW0uZGFnW2RhZzw9MC41XSA8LSAwCgpjb2xuYW1lcyh0cmltLmRhZykgPC0gcm93bmFtZXModHJpbS5kYWcpIDwtIG5hbWVzKGRpc3QpCmBgYAoKVGhlIGdyb3VwaW5nIHZhcmlhYmxlIGlzIGBmYXJtYCBhbmQgd2UgYXBwbHkgdGhlIHJhbmRvbSBlZmZlY3QgdG8gZXZlcnkgbm9kZXMuIE9uIGEgcGVyc29uYWwgY29tcHV0ZXIgZm9sbG93aW5nIGNvZGUgdGFrZXMgMTUgbWludXRlcyB3aGVyZSB0aGUgcHJldmlvdXMgY29kZSByYW4gaW4gbGVzcyB0aGFuIGEgc2Vjb25kISBUaGUgZ3JvdXBpbmcgdmFyaWFibGUgaGFzIDE1IGxldmVscy4gCgpgYGB7ciwgZXZhbD1GQUxTRSwgZWNobz1UUlVFfQptYXJnLmYuZ3JvdXBlZCA8LSBmaXRhYm4oZGFnLm0gPSB0cmltLmRhZywgCiAgICAgICAgICAgICAgICAgZGF0YS5kZiA9IGFzLmRhdGEuZnJhbWUoYWJuZGF0YSksCiAgICAgICAgICAgICAgICAgZGF0YS5kaXN0cyA9IGRpc3QsIAogICAgICAgICAgICAgICAgIGdyb3VwLnZhciA9ICJmYXJtIiwKICAgICAgICAgICAgICAgICBjb3IudmFycyA9IGMoIkFSIiwgInBuZXVtUyIsICJmZW1hbGUiLCAibGl2ZGFtIiwgImVnZ3MiLCAid29ybUNvdW50IiwgImFnZSIsICJhZGciKSwKICAgICAgICAgICAgICAgICBjb21wdXRlLmZpeGVkID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgbi5ncmlkID0gMTAwMCkKYGBgCgojIyBWaXN1YWxseSBpbnNwZWN0IHRoZSBtYXJnaW5hbCBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9ucyBvZiB0aGUgcGFyYW1ldGVycwoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD00fQojIGFscmFkeSBjb21wdXRlZCBtYXJnaW5hbHMKbG9hZChmaWxlID0gIm1hcmcuZ3JvdXBlZC5SRGF0YSIpCgpwYXIobWZyb3c9YygxLDQpLCBtYXI9YygyLDIsMS41LDEpKQpmb3IoaSBpbiAxOmxlbmd0aChtYXJnLmYuZ3JvdXBlZCRtYXJnaW5hbHMpKXsKCiMgZ2V0IHRoZSBtYXJnaW5hbCBmb3IgY3VycmVudCBub2RlLCB3aGljaCBpcyBhIG1hdHJpeCBbeCwgZih4KV0KICBjdXIubm9kZSA8LSBtYXJnLmYuZ3JvdXBlZCRtYXJnaW5hbHNbaV0KICBub20xIDwtIG5hbWVzKG1hcmcuZi5ncm91cGVkJG1hcmdpbmFscylbaV0KCiMgcGljayB0aGUgZmlyc3QgdmFsdWUgKGZvciBtb2RlbHMgd290aG91dCByYW5kb20gZWZmZWN0cykKICBjdXIubm9kZSA8LSBjdXIubm9kZVtbMV1dCiAgZm9yKGogaW4gMTpsZW5ndGgoY3VyLm5vZGUpICkgewogICAgbm9tMiA8LSBuYW1lcyhjdXIubm9kZSlbal0KICAgIGN1ci5wYXJhbSA8LSBjdXIubm9kZVtbal1dCiAgICBwbG90KGN1ci5wYXJhbSx0eXBlPSJsIixtYWluPXBhc3RlKG5vbTEsICI6Iiwgbm9tMiksIGNleD0wLjcpCiAgfQp9CmBgYAoKIyMgQ2FsY3VsYXRlIHRoZSBhcmVhIHVuZGVyIHRoZSBkZW5zaXR5IGN1cnZlCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTR9CiNleHRyYWN0IG1hcmdpbmFscyAKbWFyZy5kZW5zIDwtIG1hcmcuZiRtYXJnaW5hbHNbWzFdXQpmb3IgKGkgaW4gMjpsZW5ndGgobWFyZy5mJG1hcmdpbmFscykpIHsKICBtYXJnLmRlbnMgPC0gYyhtYXJnLmRlbnMsIG1hcmcuZiRtYXJnaW5hbHNbW2ldXSkKfQoKIyBleHRyYWN0IG1hcmdpbmFscyBhZGp1c3RlZCBmb3IgZ3JvdXBlZCBkYXRhCm1hcmcuZGVucy5ncm91cGVkIDwtIG1hcmcuZi5ncm91cGVkJG1hcmdpbmFsc1tbMV1dCmZvciAoaSBpbiAyOmxlbmd0aChtYXJnLmYuZ3JvdXBlZCRtYXJnaW5hbHMpKSB7CiAgbWFyZy5kZW5zLmdyb3VwZWQgPC0gYyhtYXJnLmRlbnMuZ3JvdXBlZCwgbWFyZy5mLmdyb3VwZWQkbWFyZ2luYWxzW1tpXV0pCn0KCiMgY2FsY3VsYXRlIEFVQyAtLT4gc2hvdWxkIGJlIH4xCmF1YyA8LSByZXAoTkEsIGxlbmd0aChtYXJnLmRlbnMuZ3JvdXBlZCkpCm5hbWVzKGF1YykgPC0gbmFtZXMobWFyZy5kZW5zLmdyb3VwZWQpCmZvcihpIGluIDE6bGVuZ3RoKG1hcmcuZGVucy5ncm91cGVkKSkgewogIHRtcCA8LSBzcGxpbmUobWFyZy5kZW5zLmdyb3VwZWRbW2ldXSkKICBhdWNbaV0gPC0gc3VtKGRpZmYodG1wJHhbLWxlbmd0aCh0bXAkeCldKSp0bXAkeVstMV0pCn0KCmthYmxlKGNiaW5kKGF1YykpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBGQUxTRSkKCnBhcihsYXM9MiwgbWFyPWMoOC4xLDQuMSw0LjEsMi4xKSkKYmFycGxvdChhdWMsIHlsYWI9IkFyZWEgdW5kZXIgRGVuc2l0eSIsIHlsaW09YygwLDEuMikpCmBgYAoKJm5ic3A7CgpIZXJlIHRoZSAqc2hhcGUqIG9mIHNvbWUgb2YgdGhlIHBhcmFtZXRlciBkaXN0cmlidXRpb24gbG9va3Mgd2VpcmQuIEluZGVlZCwgdGhlcmUgaXMgYSBkZWFsIG9mIGluZm9ybWF0aW9uIGJldHdlZW4gdGhlIHJhbmRvbSBzdHJhdGEuIEhvd2V2ZXIsIHRoZSAqQVVDKiBsb29rcyBnb29kLiBTbyB3ZSBjYW4gcHJvY2VlZCBnZXR0aW5nIHRoZSBlc3RpbWF0ZXMuCgombmJzcDsKCiMjIEdldCB0aGUgdGFibGUgb2YgcXVhbnRpbGVzIGZvciB0aGUgbWFyZ2luYWxzCgpBcyBvbmUgY2FuIHNlZSwgZXhjZXB0IGZvciBhIHByZWNpc2lvbiBwYXJhbWV0ZXIgKGBmZW1hbGV8Z3JvdXAucHJlY2lzaW9uYCkgdGhlIHF1YW50aWxlcyBzZWVtcyB0byBub3QgaGF2ZSB0b28gbXVjaCB3aWRlbi4gCgpgYGB7ciAsIGVjaG89RkFMU0V9CgptYXQgPC0gbWF0cml4KHJlcChOQSwgbGVuZ3RoKG1hcmcuZGVucy5ncm91cGVkKSozKSwgbmNvbD0zKQpyb3duYW1lcyhtYXQpIDwtIG5hbWVzKG1hcmcuZGVucy5ncm91cGVkKQpjb2xuYW1lcyhtYXQpIDwtIGMoIjIuNSUiLCAiNTAlIiwgIjk3LjUlIikKaWdub3JlLm1lIDwtIHVuaW9uKGdyZXAoIlxcKEludCIsIG5hbWVzKG1hcmcuZGVucy5ncm91cGVkKSksIGdyZXAoInByZWMiLCBuYW1lcyhtYXJnLmRlbnMuZ3JvdXBlZCkpKSAjIHRha2UgYXdheSBiYWNrZ3JvdW5kIGsgYW5kIHByZWNpc2lvbgpjb21tZW50IDwtIHJlcCgiIiwgbGVuZ3RoKG1hcmcuZGVucy5ncm91cGVkKSkKZm9yIChpIGluIDE6bGVuZ3RoKG1hcmcuZGVucy5ncm91cGVkKSkgewogIHRtcCA8LSBtYXJnLmRlbnMuZ3JvdXBlZFtbaV1dCiAgdG1wMiA8LSBjdW1zdW0odG1wWywyXSkvc3VtKHRtcFssMl0pCiAgbWF0W2ksIF0gPC1jKHRtcFt3aGljaCh0bXAyPjAuMDI1KVsxXS0xLDFdLCMjIC0xIGlzIHNvIHVzZSB2YWx1ZSBvbiB0aGUgbGVmdCBvZiB0aGUgMi41JQogICAgICAgICAgICAgICB0bXBbd2hpY2godG1wMj4wLjUpWzFdLDFdLAogICAgICAgICAgICAgICB0bXBbd2hpY2godG1wMj4wLjk3NSlbMV0sMV0pCiAgdmVjIDwtIG1hdFtpLF0KCiAgaWYoICEoaSVpbiVpZ25vcmUubWUpICYmICh2ZWNbMV08MCAmJiB2ZWNbM10+MCkpe2NvbW1lbnRbaV08LSJub3Qgc2lnLiBhdCA1JSJ9CgogICMjIHRydW5jYXRlIGZvciBwcmludGluZwogIG1hdFtpLF0gPC0gYXMubnVtZXJpYyhmb3JtYXRDKG1hdFtpLF0sZGlnaXRzPTMsZm9ybWF0PSJmIikpCn0KCm1hdDEgPC0gbWF0cml4KHJlcChOQSwgbGVuZ3RoKG1hcmcuZGVucykqMyksIG5jb2w9MykKcm93bmFtZXMobWF0MSkgPC0gbmFtZXMobWFyZy5kZW5zKQpjb2xuYW1lcyhtYXQxKSA8LSBjKCIyLjUlIiwgIjUwJSIsICI5Ny41JSIpCmlnbm9yZS5tZSA8LSB1bmlvbihncmVwKCJcXChJbnQiLCBuYW1lcyhtYXJnLmRlbnMpKSwgZ3JlcCgicHJlYyIsIG5hbWVzKG1hcmcuZGVucykpKSAjIHRha2UgYXdheSBiYWNrZ3JvdW5kIGsgYW5kIHByZWNpc2lvbgpjb21tZW50IDwtIHJlcCgiIiwgbGVuZ3RoKG1hcmcuZGVucykpCmZvciAoaSBpbiAxOmxlbmd0aChtYXJnLmRlbnMpKSB7CiAgdG1wIDwtIG1hcmcuZGVuc1tbaV1dCiAgdG1wMiA8LSBjdW1zdW0odG1wWywyXSkvc3VtKHRtcFssMl0pCiAgbWF0MVtpLCBdIDwtYyh0bXBbd2hpY2godG1wMj4wLjAyNSlbMV0tMSwxXSwjIyAtMSBpcyBzbyB1c2UgdmFsdWUgb24gdGhlIGxlZnQgb2YgdGhlIDIuNSUKICAgICAgICAgICAgICAgdG1wW3doaWNoKHRtcDI+MC41KVsxXSwxXSwKICAgICAgICAgICAgICAgdG1wW3doaWNoKHRtcDI+MC45NzUpWzFdLDFdKQogIHZlYyA8LSBtYXQxW2ksXQoKICBpZiggIShpJWluJWlnbm9yZS5tZSkgJiYgKHZlY1sxXTwwICYmIHZlY1szXT4wKSl7Y29tbWVudFtpXTwtIm5vdCBzaWcuIGF0IDUlIn0KCiAgIyMgdHJ1bmNhdGUgZm9yIHByaW50aW5nCiAgbWF0MVtpLF0gPC0gYXMubnVtZXJpYyhmb3JtYXRDKG1hdDFbaSxdLGRpZ2l0cz0zLGZvcm1hdD0iZiIpKQp9CiAKbWF0MSAlPiUKICBrYWJsZSgiaHRtbCIsIGFsaWduID0gJ2NsYycsIGNhcHRpb24gPSAnTm9uLWFkanVzdGVkIG1hcmdpbmFsIGRlbnNpdGllcycpICU+JQogICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEZBTFNFLCBwb3NpdGlvbiA9ICJmbG9hdF9sZWZ0IikKCm1hdCAlPiUKICBrYWJsZSgiaHRtbCIsIGFsaWduID0gJ2NsYycsIGNhcHRpb24gPSAnTWFyZ2luYWwgZGVuc2l0aWVzIGFkanVzdGVkIHdpdGggcmFuZG9tIGVmZmVjdHMgJykgJT4lCiAgICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gRkFMU0UsIHBvc2l0aW9uID0gImxlZnQiKQoKYGBgCgojIEhldXJpc3RpYyBzZWFyY2gKClRoZSBgbW9zdHByb2JhYmxlKClgIGZ1bmN0aW9uIGlzIGxpbWl0ZWQgdG8gMjAgdG8gMjUgbm9kZXMgKGlmIGNvbXB1dGVkIG9uIGEgY29tcHV0aW5nIGNsdXN0ZXIgZXZlbiB3aXRoIGEgbGltaXRlZCBudW1iZXIgb2YgcGFyZW50KS4gVGhlbiBmb3IgbGFyZ2VyIHByb2JsZW0gdGhpcyBhcHByb2FjaCBiZWNvbWVzIGludHJhY3RhYmxlIGZvcm0gYSBjb21wdXRhdGlvbmFsIHBvaW50IG9mIHZpZXcuIFRoZSBtYWluIGFkdmFudGFnZXMgb2YgdGhlIGBtb3N0cHJvYmFibGUoKWAgZnVuY3Rpb24gaXMgdGhhdCB0aGUgcmV0dXJuZWQgc3RydWN0dXJlIGlzIHRoZSBvbmUgdGhhdCBoYXMgdGhlIG1heGltYWwgcG9zc2libGUgc2NvcmUsIHRodXMgaXQgaXMgY2FsbGVkICoqZXhhY3QqKiBpbiB0aGF0IHNlbnNlIFtAa29pdmlzdG8yMDA0ZXhhY3RdLiBJdCBjb21wYXJlcyBhbGwgcG9zc2libGUgc3RydWN0dXJlIHRvIHNlbGVjdCB0aGUgb3B0aW1hbCBvbmUuIAoKVGhlIGhldXJpc3RpYyBzZWFyY2hlcyBhcmUgdGVjaG5pcXVlcyB0aGF0IHRlbmRzIHRvIHBlcmZvcm0gYSBncmVlZHkgb3B0aW1pemF0aW9uLiBUaGVuIHRoZXJlIGlzIG5vIGd1YXJhbnRlZXMgdG8gcmVhY2ggdGhlIG1heGltdW0gc2NvcmUuIEluIHRoaXMgY29udGV4dCBncmVlZHkgbWVhbnM6IHBlcmZvcm0gbG9jYWwgb3B0aW1pemF0aW9uIGFuZCBob3BpbmcgdG8gZ2V0IHRoZSBvdmVyYWxsIG1heGltdW0uCgpXZSB3aWxsIHVzZSBhIEhpbGwtQ2xpbWJlciBbQGtvcmIyMDEwYmF5ZXNpYW5dLiBXZSBuZWVkIHRvIHNldCB1cCB0aGUgbnVtYmVyIG9mIHNlYXJjaGVzIGFuZCB0aGUgbnVtYmVyIG9mIHN0ZXBzIHBlciBzZWFyY2hlcy4gVGhlIHN0YXJ0aW5nIHBvaW50IChoZXJlIHdlIGNob29zZSBhIHJhbmRvbSBEaXJlY3RlZCBBY3ljbGljIEdyYXBoIChEQUcpKS4gVGhlIHJlc3Qgb2YgcGFyYW1ldGVycyBpcyBlcXVpdmFsZW50IHRvIHRoZSBvbmVzIHVzZWQgaW4gYGJ1aWxkc2NvcmVjYWNoZSgpYCBmdW5jdGlvbi4KCmBgYHtyIGhldXJpc3RpYzEsIGVjaG89VFJVRSwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTR9CgphYm5kYXRhIDwtIGR0WywgLWRyb3BdCgphYm5kYXRhWywxOjVdIDwtIGFzLmRhdGEuZnJhbWUobGFwcGx5KGFibmRhdGFbLDE6NV0sIGZhY3RvcikpCmRpc3QgPC0gbGlzdChBUiA9ICJiaW5vbWlhbCIsIHBuZXVtUyA9ICJiaW5vbWlhbCIsIGZlbWFsZT0iYmlub21pYWwiLCAKICAgICAgICAgICAgIGxpdmRhbT0gImJpbm9taWFsIiwgZWdncyA9ICJiaW5vbWlhbCIsd29ybUNvdW50ID0gInBvaXNzb24iLAogICAgICAgICAgICAgYWdlPSAiZ2F1c3NpYW4iLCBhZGcgPSAiZ2F1c3NpYW4iKQoKI3NldCBtYXhpbXVtIG51bWJlciBvZiBwb3NzaWJsZSBwYXJlbnQgcGVyIG5vZGUKbWF4LnBhciA8LSA0CgojIGNvbXB1dGUgYSBjYWNoZSBvZiBzY29yZXMKbXljYWNoZSA8LSBidWlsZHNjb3JlY2FjaGUoZGF0YS5kZiA9IGFzLmRhdGEuZnJhbWUoYWJuZGF0YSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmRpc3RzID0gZGlzdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heC5wYXJlbnRzID0gbWF4LnBhcikKCiMgc2V0IG51bWJlciBvZiBzZWFyY2hlcyBhbmQgbnVtYmVyIG9mIHN0ZXBzCm51bS5zZWFyY2hlcyA8LSAyMDAKbWF4LnN0ZXBzIDwtIDE1MAoKIyBIaWxsLWNsaW1iZXIKaGV1ci5yZXMgPC0gcXVpZXQoc2VhcmNoLmhldXJpc3RpYyhzY29yZS5jYWNoZSA9IG15Y2FjaGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjb3JlID0gIm1saWsiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmRpc3RzID0gZGlzdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4LnBhcmVudHMgPSA0LAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFydC5kYWcgPSAicmFuZG9tIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtLnNlYXJjaGVzID0gbnVtLnNlYXJjaGVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICBtYXguc3RlcHMgPSBtYXguc3RlcHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAzMjEzLAogICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxnbyA9ICJoYyIpKQoKIyBmb3IgY29tcGFyaXNvbiBsZXQgdXMgY29tcHV0ZSB0aGUgbWF4aW11bSBleGFjdCBzY29yZQpteWRhZyA8LSBxdWlldChtb3N0cHJvYmFibGUoc2NvcmUuY2FjaGUgPSBteWNhY2hlKSkKZmFibiA8LSBmaXRhYm4oZGFnLm0gPSBteWRhZywgZGF0YS5kZiA9IGFzLmRhdGEuZnJhbWUoYWJuZGF0YSksIGRhdGEuZGlzdHMgPSBkaXN0KQoKIyBwbG90IEhpbGwtQ2xpbWJlciBzY29yZXMKZGYuaGV1ciA8LSB1bmxpc3QoaGV1ci5yZXMkc2NvcmVzKQpwbG90KE5VTEwsbHR5PTEsIHhsYWI9IkluZGV4IG9mIGhldXJpc3RpYyBzZWFyY2giLHlsYWI9IkJOIHNjb3JlIiwgeWxpbSA9IGMobWF4KHVubGlzdChkZi5oZXVyKSksbWluKHVubGlzdChkZi5oZXVyKSkpLCB4bGltID0gYygwLG51bS5zZWFyY2hlcykpCmZvcihpIGluIDE6bnVtLnNlYXJjaGVzKXsKICBpZihzdW0oaT09b3JkZXIoZGYuaGV1cixkZWNyZWFzaW5nID0gVFJVRSlbMToxMF0pKXsKICAgIHBvaW50cyh4PWkseT1kZi5oZXVyW2ldLHR5cGU9InAiLHBjaD0xOSwgY29sPXJnYigwLDAsMSwgMC44KSxsd2QgPSAyKQogICAgfWVsc2V7CiAgICBwb2ludHMoeD1pLHk9ZGYuaGV1cltpXSx0eXBlPSJwIixwY2g9MTksIGNvbD1yZ2IoMCwwLDAsIDAuMykpCiAgfQp9CnBvaW50cyh4ID0gbWF4KHVubGlzdChkZi5oZXVyKSkseSA9IG1heCh1bmxpc3QoZGYuaGV1cikpLGNvbD0icmVkIixwY2g9MTkpCmFibGluZShoID0gZmFibiRtbGlrLCBjb2w9InJlZCIsbHR5ID0gMykKCmBgYApBYm92ZSB3ZSBwbG90IHRoZSBtYXhpbXVtIHNjb3JlcyBhZnRlciAxNTAgc3RlcHMgb3V0IG9mIDIwMCBkaWZmZXJlbnQgc2VhcmNoZXMgb2YgYSBIaWxsLUNsaW1iZXIuIFRoZSBibHVlcyBkb3RlcyBhcmUgdGhlIDEwIGJlc3Qgc2VhcmNoZXMuIFRoZSByZWQgZGFzaGVkIGxpbmUgaXMgdGhlIG1heGltdW0gcG9zc2libGUgc2NvcmUuIAoKYGBge3IgaGV1cmlzdGljMiwgZWNobz1UUlVFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NH0KIyBsZXQgdXMgcGxvdCB0aGUgZXZvbHV0aW9uIG9mIHNjb3JlcyBkdXJpbmcgb3B0aW1pemF0aW9uCkxvbmcgPC0gKGhldXIucmVzJGRldGFpbGVkLnNjb3JlKQpMb25nLmFyciA8LSBhcnJheSh1bmxpc3QoTG9uZyksIGRpbSA9IGMobnJvdyhMb25nW1sxXV0pLCBuY29sKExvbmdbWzFdXSksIGxlbmd0aChMb25nKSkpCnBsb3QoTlVMTCxsdHk9MSwgeGxhYj0iTnVtYmVyIG9mIFN0ZXBzIix5bGFiPSJCTiBzY29yZSIsIHlsaW0gPSBjKG1heCh1bmxpc3QoZGYuaGV1cikpLG1pbih1bmxpc3QoZGYuaGV1cikpKSwgeGxpbSA9IGMoMCxtYXguc3RlcHMpKQpmb3IoaSBpbiAxOm51bS5zZWFyY2hlcyl7CiAgaWYoc3VtKGk9PW9yZGVyKHVubGlzdChoZXVyLnJlcyRzY29yZXMpLGRlY3JlYXNpbmcgPSBUUlVFKVsxOjEwXSkpewogICAgcG9pbnRzKHg9MToobWF4LnN0ZXBzLTEpLHk9TG9uZy5hcnJbMSwsaV0sdHlwZT0ibCIsbHR5PTEsIGNvbD1yZ2IoMCwwLDEsIDAuOCksbHdkID0gMikKICAgIH1lbHNlewogICAgcG9pbnRzKHg9MToobWF4LnN0ZXBzLTEpLHk9TG9uZy5hcnJbMSwsaV0sdHlwZT0ibCIsbHR5PTEsIGNvbD1yZ2IoMCwwLDAsIDAuMjUpKQogIH0KfQpsaW5lcyh4PTE6KG1heC5zdGVwcy0xKSx5PUxvbmcuYXJyWzEsLHdoaWNoLm1heCh1bmxpc3QoaGV1ci5yZXMkc2NvcmVzKSldLHR5cGU9ImwiLGNvbD0icmVkIixsd2Q9MykKYWJsaW5lKGggPSBmYWJuJG1saWssIGNvbD0icmVkIixsdHkgPSAzKQoKYGBgCkFib3ZlIHdlIHBsb3QgdGhlIHNjb3JlcyBpbiBmdW5jdGlvbiBvZiB0aGUgc3RlcHMgZm9yIHRoZSAyMDAgc2VhcmNoZXMuIFRoZSBibHVlIGxpbmVzIGFyZSB0aGUgMTAgYmVzdCBhbmQgdGhlIHJlZCBvbmUgaXMgKip0aGUqKiBvbmUgdGhhdCBoYXMgdGhlIGhpZ2hlc3Qgc2NvcmUuIEFzIG9uZSBjYW4gc2VlLCBpbiBzZWFyY2hlcyB0aGF0IHdvcmsgd2VsbCB0aGUgb3B0aW1pemF0aW9uIGlzIGFscmVhZHkgZ29vZCBhZnRlciA2MCBzdGVwcy4gVGhpcyBwbG90IGlzIGEgZGlhZ25vc3RpYyBwbG90IHVzZWQgdG8gc2V0IHVwIHRoZSBudW1iZXIgb2YgcmVxdWlyZWQgc3RlcHMuIAoKIyBNQ01DIG92ZXIgdGhlIHN0cnVjdHVyZXMKClVzdWFsbHksIHRoZSBvdXRwdXQgb2YgYSBCYXllc2lhbiBuZXR3b3JrIGFuYWx5c2lzIG9mIGEgZGF0YXNldCBlbmRzLXVwIHdpdGggYSBzaW5nbGUgd2VsbCBhZGp1c3RlZCBEQUcuIEZyb20gdGhlIHJlc2VhcmNoZXIgcG9pbnQgb2YgdmlldyB0aGlzIGNvdWxkIGJlIGZydXN0cmF0aW5nLiBJbmRlZWQsIHRoZSBtb2RlbCBpcyB0aGUgb25lIHRoYXQgaXMgYmVzdCBzdXBwb3J0ZWQgYnkgdGhlIGRhdGEgYnV0IHRoZSB1bmNlcnRhaW50eSBxdWFudGlmaWNhdGlvbiBpcyBtaXNzaW5nLiBDbGFzc2ljYWxseSBpbiBlcGlkZW1pb2xvZ3ksIHJlc2VhcmNoZXJzIGFyZSB1c2VkIHRvIGV4cHJlc3MgcG9pbnQgZXN0aW1hdGUgd2l0aCBhbiB1bmNlcnRhaW50eSBtZWFzdXJlLiBBbiBhcmMgaW4gYSBCYXllc2lhbiBuZXR3b3JrIGlzIGEgcG9pbnQgZXN0aW1hdGUsIHdlIHdpbGwgc2VlIGhvdyB0byBwZXJmb3JtIG1vZGVsIGF2ZXJhZ2luZy4gVGhlICpsaW5rIHN0cmVuZ3RoKiBtZWFzdXJlIGlzIGRlc2lnbmVkIHRvIGFjY291bnQgZm9yIHRoYXQuIEFuIG1vcmUgbmF0dXJhbCBhbHRlcm5hdGl2ZSBpcyB0byBwZXJmb3JtIE1DTUMgb3ZlciBzdHJ1Y3R1cmVzIFtAZnJpZWRtYW4yMDAzYmVpbmddLiAKCldlIHVzZSB0aGUgYG1jbWNhYm4oKWAgZnVuY3Rpb24gb24gdGhlIGNhY2hlIG9mIHByZS1jb21wdXRlZCBuZXR3b3JrcyBzY29yZXMuIE9uZSBuZWVkcyB0byBkZWZpbmUgdGhlIHR5cGUgb2Ygc2NvcmUgdXNlZCAoaGVyZSBpcyB0aGUgbWFyZ2luYWwgbGlrZWxpaG9vZCBgbWxpa2ApLiBUaGUgbWF4aW11bSBvZiBudW1iZXIgb2YgcGFyZW50cyBwZXIgbm9kZSAoc2FtZSBhcyB0aGUgb25lIHVzZWQgaW4gYGJ1aWxkc2NvcmVjYWNoZSgpYCkuIFRoZSBNQ01DIGxlYXJuaW5nIHNjaGVtZSwgZGVmaW5lZCBhczogbnVtYmVyIG9mIE1DTUMgc2FtcGxlcywgbnVtYmVyIG9mIHRoaW5uZWQgc2FtcGxlZCAodG8gYXZvaWQgYXV0b2NvcnJlbGF0aW9uKSBhbmQgdGhlIGxlbmd0aCBvZiB0aGUgYnVybi1pbiBwaGFzZS4gUG9zc2libHkgYSBzdGFydGluZyBEQUcgYW5kIGEgc3RydWN0dXJhbCBwcmlvci4gV2UgYWxzbyBuZWVkIHRvIHNlbGVjdCB0aGUgcmVsYXRpdmUgcHJvYmFiaWxpdHkgb2YgcGVyZm9ybWluZyByYWRpY2FsIG1vdmVzIChzaHVmZmxpbmcpLiBJbmRlZWQsIGEgbmFpdmUgTUNNQyBhcHByb2FjaCBpcyBrbm93biB0byBnZXQgdmVyeSBlYXNpbHkgc3R1Y2sgaW4gbG9jYWwgbWF4aW11bSAoZm9yIG1vcmUgZGV0YWlscyBzZWU6IFtAZ3J6ZWdvcmN6eWsyMDA4aW1wcm92aW5nO0BzdTIwMTZpbXByb3ZpbmddKS4gCgpgYGB7cixldmFsPUZBTFNFLCBlY2hvPVRSVUV9Cm1jbWMub3V0IDwtIG1jbWNhYm4oc2NvcmUuY2FjaGUgPSBteWNhY2hlLAogICAgICAgICAgICAgICAgICBzY29yZSA9ICJtbGlrIiwKICAgICAgICAgICAgICAgICAgZGF0YS5kaXN0cyA9IGRpc3QsCiAgICAgICAgICAgICAgICAgIG1heC5wYXJlbnRzID0gNCwKICAgICAgICAgICAgICAgICAgbWNtYy5zY2hlbWUgPSBjKDEwMDAsOSw1MDApLAogICAgICAgICAgICAgICAgICBzZWVkID0gMzIxLAogICAgICAgICAgICAgICAgICB2ZXJib3NlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgIHN0YXJ0LmRhZyA9ICJyYW5kb20iLAogICAgICAgICAgICAgICAgICBwcm9iLnJldiA9IDAuMDcsCiAgICAgICAgICAgICAgICAgIHByb2IubWJyID0gMC4wNywKICAgICAgICAgICAgICAgICAgcHJpb3IuY2hvaWNlID0gMSkKYGBgCgpUaGlzIGlzIGFnYWluIGNvbXB1dGF0aW9uYWxseSBjb21wbGV4IQoKSGVyIGlzIGEgcGxvdCBvZiB0aGUgTUNNQyBzYW1wbGVzLiBPbmUgY2FuIHNlZSB0aGUgc2NvcmVzIG9mIHRoZSBzdHJ1Y3R1cmVzIG9uIHRoZSB5LWF4aXMgaW4gZnVuY3Rpb24gb2YgdGhlIGluZGV4IHN0ZXBzLiBUaGUgZG90cyBhcmUgdGhlIHJhZGljYWwgbW92ZXMuIE9uZSB0aGUgcmlnaHQgc2lkZSBhIGhpc3RvZ3JhbSBzaG93cyB0aGUgbnVtYmVyIG9mIHN0cnVjdHVyZXMgd2l0aCBhIGdpdmVuIHNjb3JlLiBBcyBvbmUgY2FuIHNlZSB0aGUgaGlzdG9ncmFtIGlzIHZlcnkgcGVha2VkIG9uIHRoZSBtYXhpbXVtIHBvc3NpYmxlIHNjb3JlLiAKCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NH0KbG9hZCgibWNtYy5SZGF0YSIpCnBsb3QobWNtYy5vdXQpCmBgYAoKT25lIGNhbiBhbHNvIGRpc3BsYXkgdGhlIGN1bXVsYXRpdmUgbWF4aW11bSBzY29yZSAodXNlZCBmb3IgbmV0d29yayBzY29yZSBvcHRpbWl6YXRpb24pLgoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD00fQpwbG90KG1jbWMub3V0LG1heC5zY29yZT1UUlVFKQpgYGAKCkJ1dCB0aGUgbWFqb3IgYWR2YW50YWdlIG9mIHRoaXMgbWV0aG9kIGlzIHRoZSBwb3NzaWJpbGl0eSBvZiBxdWVyeWluZyB0aGUgTUNNQyBzYW1wbGUgdXNpbmcgYSBmb3JtdWxhIHN0YXRlbWVudC4KCmBgYHtyLCBlY2hvPVRSVUUsIGV2YWw9VFJVRX0KIyBhdmVyYWdlIGluZGl2aWR1YWwgYXJjIHN1cHBvcnQKcXVlcnkobWNtY2FibiA9IG1jbWMub3V0KQoKIyBwcm9iYWJpbGl0eSB0aGF0IHdvcm0gY291bnQgYmVpbmcgbGlua2VkIHRvIGFnZSBidXQgbm90IHRvIGZlbWFsZSBkaXJlY3RseQpxdWVyeShtY21jYWJuID0gbWNtYy5vdXQsZm9ybXVsYSA9IH53b3JtQ291bnR8YWdlLXdvcm1Db3VudHxmZW1hbGUpCgojIHByb2JhYmlsaXR5IHRoYXQgd29ybSBjb3VudCBiZWluZyBkaXJlY3RseSBsaW5rZWQgdG8gYWdlIGFuZCBhZGcgYW5kIHRoYXQgYWRnIGlzIGxpbmsgdG8gYWdlICh1bmRpcmVjdGVkKQpxdWVyeShtY21jYWJuID0gbWNtYy5vdXQsZm9ybXVsYSA9IH53b3JtQ291bnR8YWdlICsgd29ybUNvdW50fGFkZyArIGFnZXxhZGcpKwogIHF1ZXJ5KG1jbWNhYm4gPSBtY21jLm91dCxmb3JtdWxhID0gfndvcm1Db3VudHxhZ2UgKyB3b3JtQ291bnR8YWRnICsgYWRnfGFnZSkKYGBgCiMgUmVmZXJlbmNlcw==