library(tidyverse)
library(broom)
library(directlabels)
# For non-Bayesian multilevel modeling
#library(lme4)
library(lmerTest) # use lmerTest, which is like lme4, but also provides p-values for fixed effects (albeit a somewhat controversial approach...)
# For Bayesian multilevel modeling
library(rstan)
options(mc.cores = parallel::detectCores()) # Allows rstan to run on multiple cores
rstan_options(auto_write = TRUE)
library(rethinking)
Fitting varying intercepts was fun–but we don’t have to stop there! Multilevel models are highly flexible, and there are lots of different structures you can use when choosing which parameters you want to add “varying” effects for. As a helpful rule of thumb:
“Any batch of parameters with exchangeable index values can and probably should be pooled. Exchangeable just means the index values have no true ordering, because they are arbitrary lables. There’s nothing special about intercepts; slopes can also vary by unit in the data, and pooling information among them makes better use of the data.” ~ McElreath, p. 435
Run a (non-Bayesian) varying intercepts + varying slopes model (lme4)
Next, for example, we may be interested in exploring the idea that women’s salaries could be increasing at a different rate than men’s salaries during their tenure as state employees. We could theorize that men may advocate more strongly for themselves and their career advancement, resulting in a faster increase in their hourly wages for each additional year served, compared to their female counterparts. Or, we could theorize that women’s work is recognized to be consistently excellent, resulting in more frequent recognition from their superiors in the form of higher wage increases each year than their male counterparts. These kinds of relationships–where we expect not just the intercepts, but also the rate of change to vary across groups–requires a varying slopes model.
Before we get started, let’s consider the quality of our gender data for a moment. During the data prep phase, we weren’t able to effectively guess the genders for everyone in the dataset, which resulted in individuals being labeled as ‘unknown’ for their gender value. This particular label exhibits an important bias, since it represents a group of employees who have names that may be more frequently associated with individuals with an immigrant background, or whose families have chosen to give them names that are outside of the American mainstream. At this point, we should be concerned about forging ahead, since this seems to suggest a systematic bias introduced into the data by the gender guessing process! What we are actually measuring with this “gender” variable is a proxy for something different from “gender” alone–it also seems to reflect some potential information about employees’ ethnic or family background in a way that will confuse our analysis. Because we still want to understand more about multilevel modeling, we will proceed with the analysis for didactic purposes only, but we should not attempt to draw any “real” conclusions from this point onwards!
hr_2019_top_25_job_classes %>%
filter(GENDER == 'unknown') %>%
group_by(FIRST_NAME) %>%
summarise(count_of_employees = n()) %>%
arrange(desc(count_of_employees)) %>%
top_n(10)
Convert this “gender” variable to both a factor and an index:
hr_2019_top_25_job_classes$GENDER_FACTOR <- relevel(as.factor(hr_2019_top_25_job_classes$GENDER), ref="male")
hr_2019_top_25_job_classes$GENDER_INDEX <- as.integer(recode(hr_2019_top_25_job_classes$GENDER, male = '1', female = '2', unknown = '3'))
GENDER_LABELS <- levels(hr_2019_top_25_job_classes$GENDER_FACTOR)
Then we can add this as a “varying slopes” variable and run a new multilevel model. We do this by adding the expression (YRS_SINCE_ORIGINAL_HIRE|GENDER_FACTOR) to the model formula. By default, this adds two new parameters to the model in order to estimate: 1) varying intercepts for each of the gender values, and 2) varying slopes that describe an offset, broken down by gender, from the YRS_SINCE_ORIGINAL_HIRE beta coefficient estimate. Note: If we wanted to add to add only the varying slope and not an additional group of varying intercepst for gender, we could use (0 + YRS_SINCE_ORIGINAL_HIRE|GENDER_FACTOR) the formula syntax. That would tell lmer not to fit an intercept estimate for the related grouping variable.
model3_lmer <- lmer(COMP_RATE_STND_HOURLY ~ YRS_SINCE_ORIGINAL_HIRE + (1|JOB_FACTOR) + (YRS_SINCE_ORIGINAL_HIRE|GENDER_FACTOR),
data = hr_2019_top_25_job_classes, REML = FALSE)
boundary (singular) fit: see ?isSingular
options(scipen=999)
summary(model3_lmer)
Linear mixed model fit by maximum likelihood . t-tests use Satterthwaite's method ['lmerModLmerTest']
Formula: COMP_RATE_STND_HOURLY ~ YRS_SINCE_ORIGINAL_HIRE + (1 | JOB_FACTOR) +
(YRS_SINCE_ORIGINAL_HIRE | GENDER_FACTOR)
Data: hr_2019_top_25_job_classes
AIC BIC logLik deviance df.resid
81544.6 81598.7 -40765.3 81530.6 16708
Scaled residuals:
Min 1Q Median 3Q Max
-11.4703 -0.5285 0.0434 0.6163 6.9850
Random effects:
Groups Name Variance Std.Dev. Corr
JOB_FACTOR (Intercept) 75.4230293 8.68464
GENDER_FACTOR (Intercept) 0.0008046 0.02836
YRS_SINCE_ORIGINAL_HIRE 0.0002304 0.01518 -1.00
Residual 7.5863136 2.75433
Number of obs: 16715, groups: JOB_FACTOR, 25; GENDER_FACTOR, 3
Fixed effects:
Estimate Std. Error df t value Pr(>|t|)
(Intercept) 27.365139 1.737373 31.549430 15.75 < 0.0000000000000002 ***
YRS_SINCE_ORIGINAL_HIRE 0.176865 0.009606 1.989306 18.41 0.00301 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Correlation of Fixed Effects:
(Intr)
YRS_SINCE_O -0.013
convergence code: 0
boundary (singular) fit: see ?isSingular
The model summary reveals that the GENDER_FACTOR variable adds very little, if anything, to the model. The AIC for this model is slightly lower (aka “better”) than the AIC for the AIC for the model that contained only JOB_FACTOR as a varying intercept. But the contribution seems minimal. When we run the Bayesian model below, we’ll examine the credible intervals for these gender-related intercept and slope parameters a bit more closely, but for now it’s safe to say that the GENDER_FACTOR doesn’t add anything exciting here. Which is a good thing–we don’t actually want to see gender playing a significant role in compensation patterns for state employees!
Just for illustrative purposes, what does this look like? The structure of the model is getting pretty complex now, so we’ll filter it down to show only a few job classes at a time to try to illustrate. The model now contains separate intercepts for each job class and for each gender value. The slope of the relationship between years worked and hourly compensation rate is now variable by gender, so we should see that the lines now have slightly different slopes across gender values. In the plot below, the varying slopes by gender are easy to spot. The varying intercepts by gender are also in there–they’re just too minuscule to see!
rand <- as.data.frame(ranef(model3_lmer))
gender_coeff_df <- rand %>%
filter(grpvar == 'GENDER_FACTOR') %>%
mutate(term = recode(term, `(Intercept)` = 'intercept_gender', `YRS_SINCE_ORIGINAL_HIRE` = 'slope_gender')) %>%
select(-grpvar, -condsd) %>%
spread(term, condval)
job_coeff_df <- rand %>%
filter(grpvar == 'JOB_FACTOR') %>%
mutate(term = recode(term, `(Intercept)` = 'intercept_job')) %>%
select(-grpvar, -condsd) %>%
spread(term, condval)
x <- as.data.frame(seq(0, 30, 1)) # set up x axis data to visualize 30 years
names(x) <- "YRS_SINCE_ORIGINAL_HIRE"
j <- as.data.frame(c('Laborer General', 'Registered Nurse', 'Transp Specialist', 'Corr Officer 2'))
names(j) <- "JOB_FACTOR"
z <- as.data.frame(c('male', 'female', 'unknown'))
names(z) <- "GENDER_FACTOR"
viz_df <- crossing(x, j, z)
viz_df <- inner_join(viz_df, gender_coeff_df, by=c('GENDER_FACTOR' = 'grp')) %>%
inner_join(., job_coeff_df, by=c('JOB_FACTOR' = 'grp')) %>%
mutate(
intercept_pop = fixef(model2_lmer)['(Intercept)'],
slope_pop = fixef(model2_lmer)['YRS_SINCE_ORIGINAL_HIRE']
) %>%
mutate(salary_est = intercept_pop + intercept_job + intercept_gender + YRS_SINCE_ORIGINAL_HIRE * (slope_pop + slope_gender))
Column `GENDER_FACTOR`/`grp` joining factors with different levels, coercing to character vectorColumn `JOB_FACTOR`/`grp` joining factors with different levels, coercing to character vector
ggplot(viz_df, aes(x=YRS_SINCE_ORIGINAL_HIRE, y=salary_est, col=JOB_FACTOR, grp=GENDER_FACTOR, label=GENDER_FACTOR)) +
geom_line(aes(linetype=GENDER_FACTOR)) +
xlim(0, 50) +
ylim(0, 45)

Run a Bayesian varying intercepts + varying slopes model (rethinking)
Now, we can run the same model, but with a Bayesian approach. This practice of setting priors for this kind of model gets really weird, so it’s worth having a look at Ch. 14 in the McElrath textbook to build some intuition around these. I made my best attempt to describe what I think each prior is doing in the comments below, but I do not claim to be enough of a “math person” to have totally figured these out:
fit_model3_bayes <- function() {
# Prep the data columns we want to pass the the model
dat_list <- list(
COMP_RATE_STND_HOURLY = hr_2019_top_25_job_classes$COMP_RATE_STND_HOURLY,
JOB_INDEX = hr_2019_top_25_job_classes$JOB_INDEX,
YRS_SINCE_ORIGINAL_HIRE = hr_2019_top_25_job_classes$YRS_SINCE_ORIGINAL_HIRE,
GENDER_INDEX = hr_2019_top_25_job_classes$GENDER_INDEX
)
tic("Running model 3 Bayesian")
model3_bayes <- ulam(
alist(
COMP_RATE_STND_HOURLY ~ dnorm( mu , sigma ),
mu <- Intercept + a_job[JOB_INDEX] + a_gender[GENDER_INDEX] + (b_YRS_SINCE_ORIGINAL_HIRE + b_gender[GENDER_INDEX])*YRS_SINCE_ORIGINAL_HIRE,
# population priors
Intercept ~ dnorm(30, 10),
sigma ~ dcauchy(0, 30),
# fixed effects priors
b_YRS_SINCE_ORIGINAL_HIRE ~ dnorm(0.675, 1.5),
# varying intercepts priors
a_job[JOB_INDEX] ~ dnorm(0, a_job_sigma), # prior for mean hourly starting salary of each group (after accounting for the "grand intercept" for the population as a whole): a normal distribution centered around 0
a_job_sigma ~ dcauchy(0, 50), # prior for the standard deviation of the mean hourly starting salary across groups
# varying intercepts + slopes priors
c(a_gender, b_gender)[GENDER_INDEX] ~ multi_normal(0, Rho, sigma_gender), # we think intercepts and slopes are distributed along a multivariate normal distribution described by their correlation (Rho) and standard deviations
Rho ~ dlkjcorr(2), # prior for the correlation between slopes and intercepts within a group (see McElrath, p. 443 for a discussion of this prior)
sigma_gender ~ exponential(1) # ???
),
data = dat_list,
chains = 1,
log_lik = TRUE
)
toc() # stop the timer
beepr::beep() # emit a beep so we can come back from whatever else we were doing
saveRDS(model3_bayes, "./models/model3_bayes.rds")
return(model3_bayes)
}
if(file.exists("./models/model3_bayes.rds")) {
cat("Model has already been fit! Loading results from disk.")
model3_bayes <- readRDS("./models/model3_bayes.rds")
} else {
cat("Model has not yet been fit. Fitting model now. Please be patient.")
model3_bayes <- fit_model3_bayes()
}
Model has already been fit! Loading results from disk.
And we can view the model results:
precis(model3_bayes, depth=3)
plot(precis(model3_bayes, depth=2), main="All Posterior Params")

plot(precis(model3_bayes, depth=2, pars="a_job"), labels=JOB_LABELS, main="Job Intercepts")

plot(precis(model3_bayes, depth=2, pars="a_gender"), labels=GENDER_LABELS, main="Gender Intercepts")

plot(precis(model3_bayes, depth=2, pars="b_gender"), labels=GENDER_LABELS, main="Gender Slopes")

Compare the Bayesian models
We can run final comparison–this time across all of the Bayesian multilevel models we’ve fit so far. We can see that model 3, which contains job intercepts + gender intercepts and slopes, has a slightly lower WAIC value. This doesn’t, however, seem to be substantial for us to conclude that this “messy” gender variable adds a strong contribution to the model.
compare(model2_bayes, model2_bayes_extreme_priors, model3_bayes)
Conclusion
I hope this has served as a useful crash course in the basic principles behind Bayesian regression and multilevel modeling for this kind of hierarchical regression task. And above all, the intuition behind “pooling” and how you can leverage it to your advantage should be a key take-away of multilevel modeling. You may find it useful to start considering whether multilevel should become your default choice–particularly when faced with modeling situations that involve nested or hierarchical data. Let’s sum things up with a pity quote from Statistical Rethinking:
“Every model is a merger of sense and nonsense. When we understand a model, we can find its sense and control its nonsense.” ~ McElreath, p. 426
LS0tCnRpdGxlOiAiTU4gUHVibGljIFNhbGFyeSBEYXRhIC0gTXVsdGlsZXZlbCBNb2RlbHMiCnN1YnRpdGxlOiAiUGFydCAzOiBWYXJ5aW5nIHNsb3BlcyIKYXV0aG9yOiAiQWxpc29uIExpbmsiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShicm9vbSkKbGlicmFyeShkaXJlY3RsYWJlbHMpCgojIEZvciBub24tQmF5ZXNpYW4gbXVsdGlsZXZlbCBtb2RlbGluZwojbGlicmFyeShsbWU0KQpsaWJyYXJ5KGxtZXJUZXN0KSAjIHVzZSBsbWVyVGVzdCwgd2hpY2ggaXMgbGlrZSBsbWU0LCBidXQgYWxzbyBwcm92aWRlcyBwLXZhbHVlcyBmb3IgZml4ZWQgZWZmZWN0cyAoYWxiZWl0IGEgc29tZXdoYXQgY29udHJvdmVyc2lhbCBhcHByb2FjaC4uLikKCiMgRm9yIEJheWVzaWFuIG11bHRpbGV2ZWwgbW9kZWxpbmcKbGlicmFyeShyc3RhbikKb3B0aW9ucyhtYy5jb3JlcyA9IHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpKSAjIEFsbG93cyByc3RhbiB0byBydW4gb24gbXVsdGlwbGUgY29yZXMKcnN0YW5fb3B0aW9ucyhhdXRvX3dyaXRlID0gVFJVRSkKbGlicmFyeShyZXRoaW5raW5nKSAKYGBgCgpGaXR0aW5nIHZhcnlpbmcgaW50ZXJjZXB0cyB3YXMgZnVuLS1idXQgd2UgZG9uJ3QgaGF2ZSB0byBzdG9wIHRoZXJlISAgTXVsdGlsZXZlbCBtb2RlbHMgYXJlIGhpZ2hseSBmbGV4aWJsZSwgYW5kIHRoZXJlIGFyZSBsb3RzIG9mIGRpZmZlcmVudCBzdHJ1Y3R1cmVzIHlvdSBjYW4gdXNlIHdoZW4gY2hvb3Npbmcgd2hpY2ggcGFyYW1ldGVycyB5b3Ugd2FudCB0byBhZGQgInZhcnlpbmciIGVmZmVjdHMgZm9yLiAgQXMgYSBoZWxwZnVsIHJ1bGUgb2YgdGh1bWI6Cgo+ICJBbnkgYmF0Y2ggb2YgcGFyYW1ldGVycyB3aXRoIF9leGNoYW5nZWFibGVfIGluZGV4IHZhbHVlcyBjYW4gYW5kIHByb2JhYmx5IHNob3VsZCBiZSBwb29sZWQuIEV4Y2hhbmdlYWJsZSBqdXN0IG1lYW5zIHRoZSBpbmRleCB2YWx1ZXMgaGF2ZSBubyB0cnVlIG9yZGVyaW5nLCBiZWNhdXNlIHRoZXkgYXJlIGFyYml0cmFyeSBsYWJsZXMuIFRoZXJlJ3Mgbm90aGluZyBzcGVjaWFsIGFib3V0IGludGVyY2VwdHM7IHNsb3BlcyBjYW4gYWxzbyB2YXJ5IGJ5IHVuaXQgaW4gdGhlIGRhdGEsIGFuZCBwb29saW5nIGluZm9ybWF0aW9uIGFtb25nIHRoZW0gbWFrZXMgYmV0dGVyIHVzZSBvZiB0aGUgZGF0YS4iIH4gTWNFbHJlYXRoLCBwLiA0MzUKCgojIyBSdW4gYSAobm9uLUJheWVzaWFuKSB2YXJ5aW5nIGludGVyY2VwdHMgKyB2YXJ5aW5nIHNsb3BlcyBtb2RlbCAoYGxtZTRgKQoKTmV4dCwgZm9yIGV4YW1wbGUsIHdlIG1heSBiZSBpbnRlcmVzdGVkIGluIGV4cGxvcmluZyB0aGUgaWRlYSB0aGF0IHdvbWVuJ3Mgc2FsYXJpZXMgY291bGQgYmUgX2luY3JlYXNpbmdfIGF0IGEgZGlmZmVyZW50IHJhdGUgdGhhbiBtZW4ncyBzYWxhcmllcyBkdXJpbmcgdGhlaXIgdGVudXJlIGFzIHN0YXRlIGVtcGxveWVlcy4gIFdlIGNvdWxkIHRoZW9yaXplIHRoYXQgbWVuIG1heSBhZHZvY2F0ZSBtb3JlIHN0cm9uZ2x5IGZvciB0aGVtc2VsdmVzIGFuZCB0aGVpciBjYXJlZXIgYWR2YW5jZW1lbnQsIHJlc3VsdGluZyBpbiBhIGZhc3RlciBpbmNyZWFzZSBpbiB0aGVpciBob3VybHkgd2FnZXMgZm9yIGVhY2ggYWRkaXRpb25hbCB5ZWFyIHNlcnZlZCwgY29tcGFyZWQgdG8gdGhlaXIgZmVtYWxlIGNvdW50ZXJwYXJ0cy4gIE9yLCB3ZSBjb3VsZCB0aGVvcml6ZSB0aGF0IHdvbWVuJ3Mgd29yayBpcyByZWNvZ25pemVkIHRvIGJlIGNvbnNpc3RlbnRseSBleGNlbGxlbnQsIHJlc3VsdGluZyBpbiBtb3JlIGZyZXF1ZW50IHJlY29nbml0aW9uIGZyb20gdGhlaXIgc3VwZXJpb3JzIGluIHRoZSBmb3JtIG9mIGhpZ2hlciB3YWdlIGluY3JlYXNlcyBlYWNoIHllYXIgdGhhbiB0aGVpciBtYWxlIGNvdW50ZXJwYXJ0cy4gIFRoZXNlIGtpbmRzIG9mIHJlbGF0aW9uc2hpcHMtLXdoZXJlIHdlIGV4cGVjdCBub3QganVzdCB0aGUgaW50ZXJjZXB0cywgYnV0IGFsc28gdGhlIHJhdGUgb2YgY2hhbmdlIHRvIHZhcnkgYWNyb3NzIGdyb3Vwcy0tcmVxdWlyZXMgYSB2YXJ5aW5nIF9zbG9wZXNfIG1vZGVsLgoKQmVmb3JlIHdlIGdldCBzdGFydGVkLCBsZXQncyBjb25zaWRlciB0aGUgcXVhbGl0eSBvZiBvdXIgZ2VuZGVyIGRhdGEgZm9yIGEgbW9tZW50LiAgRHVyaW5nIHRoZSBkYXRhIHByZXAgcGhhc2UsIHdlIHdlcmVuJ3QgYWJsZSB0byBlZmZlY3RpdmVseSBndWVzcyB0aGUgZ2VuZGVycyBmb3IgZXZlcnlvbmUgaW4gdGhlIGRhdGFzZXQsIHdoaWNoIHJlc3VsdGVkIGluIGluZGl2aWR1YWxzIGJlaW5nIGxhYmVsZWQgYXMgJ3Vua25vd24nIGZvciB0aGVpciBnZW5kZXIgdmFsdWUuICBUaGlzIHBhcnRpY3VsYXIgbGFiZWwgZXhoaWJpdHMgYW4gaW1wb3J0YW50IGJpYXMsIHNpbmNlIGl0IHJlcHJlc2VudHMgYSBncm91cCBvZiBlbXBsb3llZXMgd2hvIGhhdmUgbmFtZXMgdGhhdCBtYXkgYmUgbW9yZSBmcmVxdWVudGx5IGFzc29jaWF0ZWQgd2l0aCBpbmRpdmlkdWFscyB3aXRoIGFuIGltbWlncmFudCBiYWNrZ3JvdW5kLCBvciB3aG9zZSBmYW1pbGllcyBoYXZlIGNob3NlbiB0byBnaXZlIHRoZW0gbmFtZXMgdGhhdCBhcmUgb3V0c2lkZSBvZiB0aGUgQW1lcmljYW4gbWFpbnN0cmVhbS4gIEF0IHRoaXMgcG9pbnQsIHdlIHNob3VsZCBiZSBjb25jZXJuZWQgYWJvdXQgZm9yZ2luZyBhaGVhZCwgc2luY2UgdGhpcyBzZWVtcyB0byBzdWdnZXN0IGEgc3lzdGVtYXRpYyBiaWFzIGludHJvZHVjZWQgaW50byB0aGUgZGF0YSBieSB0aGUgZ2VuZGVyIGd1ZXNzaW5nIHByb2Nlc3MhICBXaGF0IHdlIGFyZSBhY3R1YWxseSBtZWFzdXJpbmcgd2l0aCB0aGlzICJnZW5kZXIiIHZhcmlhYmxlIGlzIGEgcHJveHkgZm9yIHNvbWV0aGluZyBkaWZmZXJlbnQgZnJvbSAiZ2VuZGVyIiBhbG9uZS0taXQgYWxzbyBzZWVtcyB0byByZWZsZWN0IHNvbWUgcG90ZW50aWFsIGluZm9ybWF0aW9uIGFib3V0IGVtcGxveWVlcycgZXRobmljIG9yIGZhbWlseSBiYWNrZ3JvdW5kIGluIGEgd2F5IHRoYXQgd2lsbCBjb25mdXNlIG91ciBhbmFseXNpcy4gIEJlY2F1c2Ugd2Ugc3RpbGwgd2FudCB0byB1bmRlcnN0YW5kIG1vcmUgYWJvdXQgbXVsdGlsZXZlbCBtb2RlbGluZywgd2Ugd2lsbCBwcm9jZWVkIHdpdGggdGhlIGFuYWx5c2lzIGZvciBkaWRhY3RpYyBwdXJwb3NlcyBvbmx5LCBidXQgKip3ZSBzaG91bGQgbm90IGF0dGVtcHQgdG8gZHJhdyBhbnkgInJlYWwiIGNvbmNsdXNpb25zIGZyb20gdGhpcyBwb2ludCBvbndhcmRzISoqCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KaHJfMjAxOV90b3BfMjVfam9iX2NsYXNzZXMgJT4lIAogIGZpbHRlcihHRU5ERVIgPT0gJ3Vua25vd24nKSAlPiUKICBncm91cF9ieShGSVJTVF9OQU1FKSAlPiUKICBzdW1tYXJpc2UoY291bnRfb2ZfZW1wbG95ZWVzID0gbigpKSAlPiUKICBhcnJhbmdlKGRlc2MoY291bnRfb2ZfZW1wbG95ZWVzKSkgJT4lCiAgdG9wX24oMTApCmBgYApDb252ZXJ0IHRoaXMgImdlbmRlciIgdmFyaWFibGUgdG8gYm90aCBhIGZhY3RvciBhbmQgYW4gaW5kZXg6CgpgYGB7cn0KaHJfMjAxOV90b3BfMjVfam9iX2NsYXNzZXMkR0VOREVSX0ZBQ1RPUiA8LSByZWxldmVsKGFzLmZhY3Rvcihocl8yMDE5X3RvcF8yNV9qb2JfY2xhc3NlcyRHRU5ERVIpLCByZWY9Im1hbGUiKQpocl8yMDE5X3RvcF8yNV9qb2JfY2xhc3NlcyRHRU5ERVJfSU5ERVggPC0gYXMuaW50ZWdlcihyZWNvZGUoaHJfMjAxOV90b3BfMjVfam9iX2NsYXNzZXMkR0VOREVSLCBtYWxlID0gJzEnLCBmZW1hbGUgPSAnMicsIHVua25vd24gPSAnMycpKQoKR0VOREVSX0xBQkVMUyA8LSBsZXZlbHMoaHJfMjAxOV90b3BfMjVfam9iX2NsYXNzZXMkR0VOREVSX0ZBQ1RPUikKYGBgCgpUaGVuIHdlIGNhbiBhZGQgdGhpcyBhcyBhICJ2YXJ5aW5nIHNsb3BlcyIgdmFyaWFibGUgYW5kIHJ1biBhIG5ldyBtdWx0aWxldmVsIG1vZGVsLiAgV2UgZG8gdGhpcyBieSBhZGRpbmcgdGhlIGV4cHJlc3Npb24gYChZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRXxHRU5ERVJfRkFDVE9SKWAgdG8gdGhlIG1vZGVsIGZvcm11bGEuIEJ5IGRlZmF1bHQsIHRoaXMgYWRkcyB0d28gbmV3IHBhcmFtZXRlcnMgdG8gdGhlIG1vZGVsIGluIG9yZGVyIHRvIGVzdGltYXRlOiAxKSB2YXJ5aW5nIGludGVyY2VwdHMgZm9yIGVhY2ggb2YgdGhlIGdlbmRlciB2YWx1ZXMsIGFuZCAyKSB2YXJ5aW5nIHNsb3BlcyB0aGF0IGRlc2NyaWJlIGFuIG9mZnNldCwgYnJva2VuIGRvd24gYnkgZ2VuZGVyLCBmcm9tIHRoZSBZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRSBiZXRhIGNvZWZmaWNpZW50IGVzdGltYXRlLiAgX05vdGU6XyBJZiB3ZSB3YW50ZWQgdG8gYWRkIHRvIGFkZCBfb25seV8gdGhlIHZhcnlpbmcgc2xvcGUgYW5kIF9ub3RfIGFuIGFkZGl0aW9uYWwgZ3JvdXAgb2YgdmFyeWluZyBpbnRlcmNlcHN0IGZvciBnZW5kZXIsIHdlIGNvdWxkIHVzZSBgKDAgKyBZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRXxHRU5ERVJfRkFDVE9SKWAgdGhlIGZvcm11bGEgc3ludGF4LiAgVGhhdCB3b3VsZCB0ZWxsIGxtZXIgX25vdF8gdG8gZml0IGFuIGludGVyY2VwdCBlc3RpbWF0ZSBmb3IgdGhlIHJlbGF0ZWQgZ3JvdXBpbmcgdmFyaWFibGUuCiAKYGBge3IgbW9kZWxfdmFyX2ludF92YXJfc2xvcGVfbG1lcn0KbW9kZWwzX2xtZXIgPC0gbG1lcihDT01QX1JBVEVfU1RORF9IT1VSTFkgfiBZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRSArICgxfEpPQl9GQUNUT1IpICsgKFlSU19TSU5DRV9PUklHSU5BTF9ISVJFfEdFTkRFUl9GQUNUT1IpLCAKICAgICAgICAgICAgICAgICAgICBkYXRhID0gaHJfMjAxOV90b3BfMjVfam9iX2NsYXNzZXMsIFJFTUwgPSBGQUxTRSkKCm9wdGlvbnMoc2NpcGVuPTk5OSkKc3VtbWFyeShtb2RlbDNfbG1lcikKYGBgCgpUaGUgbW9kZWwgc3VtbWFyeSByZXZlYWxzIHRoYXQgdGhlIGBHRU5ERVJfRkFDVE9SYCB2YXJpYWJsZSBhZGRzIHZlcnkgbGl0dGxlLCBpZiBhbnl0aGluZywgdG8gdGhlIG1vZGVsLiAgVGhlIEFJQyBmb3IgdGhpcyBtb2RlbCBpcyBfc2xpZ2h0bHlfIGxvd2VyIChha2EgImJldHRlciIpIHRoYW4gdGhlIEFJQyBmb3IgdGhlIEFJQyBmb3IgdGhlIG1vZGVsIHRoYXQgY29udGFpbmVkIG9ubHkgYEpPQl9GQUNUT1JgIGFzIGEgdmFyeWluZyBpbnRlcmNlcHQuICBCdXQgdGhlIGNvbnRyaWJ1dGlvbiBzZWVtcyBtaW5pbWFsLiAgV2hlbiB3ZSBydW4gdGhlIEJheWVzaWFuIG1vZGVsIGJlbG93LCB3ZSdsbCBleGFtaW5lIHRoZSBjcmVkaWJsZSBpbnRlcnZhbHMgZm9yIHRoZXNlIGdlbmRlci1yZWxhdGVkIGludGVyY2VwdCBhbmQgc2xvcGUgcGFyYW1ldGVycyBhIGJpdCBtb3JlIGNsb3NlbHksIGJ1dCBmb3Igbm93IGl0J3Mgc2FmZSB0byBzYXkgdGhhdCB0aGUgYEdFTkRFUl9GQUNUT1JgIGRvZXNuJ3QgYWRkIGFueXRoaW5nIGV4Y2l0aW5nIGhlcmUuICBXaGljaCBpcyBhIGdvb2QgdGhpbmctLXdlIGRvbid0IGFjdHVhbGx5IF93YW50XyB0byBzZWUgZ2VuZGVyIHBsYXlpbmcgYSBzaWduaWZpY2FudCByb2xlIGluIGNvbXBlbnNhdGlvbiBwYXR0ZXJucyBmb3Igc3RhdGUgZW1wbG95ZWVzIQoKSnVzdCBmb3IgaWxsdXN0cmF0aXZlIHB1cnBvc2VzLCB3aGF0IGRvZXMgdGhpcyBsb29rIGxpa2U/ICBUaGUgc3RydWN0dXJlIG9mIHRoZSBtb2RlbCBpcyBnZXR0aW5nIHByZXR0eSBjb21wbGV4IG5vdywgc28gd2UnbGwgZmlsdGVyIGl0IGRvd24gdG8gc2hvdyBvbmx5IGEgZmV3IGpvYiBjbGFzc2VzIGF0IGEgdGltZSB0byB0cnkgdG8gaWxsdXN0cmF0ZS4gIFRoZSBtb2RlbCBub3cgY29udGFpbnMgc2VwYXJhdGUgaW50ZXJjZXB0cyBmb3IgZWFjaCBqb2IgY2xhc3MgX2FuZF8gZm9yIGVhY2ggZ2VuZGVyIHZhbHVlLiAgVGhlIHNsb3BlIG9mIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB5ZWFycyB3b3JrZWQgYW5kIGhvdXJseSBjb21wZW5zYXRpb24gcmF0ZSBpcyBub3cgdmFyaWFibGUgYnkgZ2VuZGVyLCBzbyB3ZSBzaG91bGQgc2VlIHRoYXQgdGhlIGxpbmVzIG5vdyBoYXZlIHNsaWdodGx5IGRpZmZlcmVudCBzbG9wZXMgYWNyb3NzIGdlbmRlciB2YWx1ZXMuICBJbiB0aGUgcGxvdCBiZWxvdywgdGhlIHZhcnlpbmcgc2xvcGVzIGJ5IGdlbmRlciBhcmUgZWFzeSB0byBzcG90LiAgVGhlIHZhcnlpbmcgaW50ZXJjZXB0cyBieSBnZW5kZXIgYXJlIGFsc28gaW4gdGhlcmUtLXRoZXkncmUganVzdCB0b28gbWludXNjdWxlIHRvIHNlZSEKCmBgYHtyfQpyYW5kIDwtIGFzLmRhdGEuZnJhbWUocmFuZWYobW9kZWwzX2xtZXIpKQoKZ2VuZGVyX2NvZWZmX2RmIDwtIHJhbmQgJT4lCiAgZmlsdGVyKGdycHZhciA9PSAnR0VOREVSX0ZBQ1RPUicpICU+JQogIG11dGF0ZSh0ZXJtID0gcmVjb2RlKHRlcm0sIGAoSW50ZXJjZXB0KWAgPSAnaW50ZXJjZXB0X2dlbmRlcicsIGBZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRWAgPSAnc2xvcGVfZ2VuZGVyJykpICU+JQogIHNlbGVjdCgtZ3JwdmFyLCAtY29uZHNkKSAlPiUKICBzcHJlYWQodGVybSwgY29uZHZhbCkKCmpvYl9jb2VmZl9kZiA8LSByYW5kICU+JQogIGZpbHRlcihncnB2YXIgPT0gJ0pPQl9GQUNUT1InKSAlPiUKICBtdXRhdGUodGVybSA9IHJlY29kZSh0ZXJtLCBgKEludGVyY2VwdClgID0gJ2ludGVyY2VwdF9qb2InKSkgJT4lCiAgc2VsZWN0KC1ncnB2YXIsIC1jb25kc2QpICU+JQogIHNwcmVhZCh0ZXJtLCBjb25kdmFsKQogIAp4IDwtIGFzLmRhdGEuZnJhbWUoc2VxKDAsIDMwLCAxKSkgIyBzZXQgdXAgeCBheGlzIGRhdGEgdG8gdmlzdWFsaXplIDMwIHllYXJzCm5hbWVzKHgpIDwtICJZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRSIKCmogPC0gYXMuZGF0YS5mcmFtZShjKCdMYWJvcmVyIEdlbmVyYWwnLCAnUmVnaXN0ZXJlZCBOdXJzZScsICdUcmFuc3AgU3BlY2lhbGlzdCcsICdDb3JyIE9mZmljZXIgMicpKQpuYW1lcyhqKSA8LSAiSk9CX0ZBQ1RPUiIKCnogPC0gYXMuZGF0YS5mcmFtZShjKCdtYWxlJywgJ2ZlbWFsZScsICd1bmtub3duJykpCm5hbWVzKHopIDwtICJHRU5ERVJfRkFDVE9SIgoKdml6X2RmIDwtIGNyb3NzaW5nKHgsIGosIHopCgp2aXpfZGYgPC0gaW5uZXJfam9pbih2aXpfZGYsIGdlbmRlcl9jb2VmZl9kZiwgYnk9YygnR0VOREVSX0ZBQ1RPUicgPSAnZ3JwJykpICU+JQogIGlubmVyX2pvaW4oLiwgam9iX2NvZWZmX2RmLCBieT1jKCdKT0JfRkFDVE9SJyA9ICdncnAnKSkgJT4lCiAgbXV0YXRlKAogICAgaW50ZXJjZXB0X3BvcCA9IGZpeGVmKG1vZGVsMl9sbWVyKVsnKEludGVyY2VwdCknXSwKICAgIHNsb3BlX3BvcCA9IGZpeGVmKG1vZGVsMl9sbWVyKVsnWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUnXQogICkgJT4lCiAgbXV0YXRlKHNhbGFyeV9lc3QgPSBpbnRlcmNlcHRfcG9wICsgaW50ZXJjZXB0X2pvYiArIGludGVyY2VwdF9nZW5kZXIgKyBZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRSAqIChzbG9wZV9wb3AgKyBzbG9wZV9nZW5kZXIpKQoKZ2dwbG90KHZpel9kZiwgYWVzKHg9WVJTX1NJTkNFX09SSUdJTkFMX0hJUkUsIHk9c2FsYXJ5X2VzdCwgY29sPUpPQl9GQUNUT1IsIGdycD1HRU5ERVJfRkFDVE9SLCBsYWJlbD1HRU5ERVJfRkFDVE9SKSkgKwogIGdlb21fbGluZShhZXMobGluZXR5cGU9R0VOREVSX0ZBQ1RPUikpICsKICB4bGltKDAsIDUwKSArCiAgeWxpbSgwLCA0NSkKYGBgCgoKIyMgUnVuIGEgQmF5ZXNpYW4gdmFyeWluZyBpbnRlcmNlcHRzICsgdmFyeWluZyBzbG9wZXMgbW9kZWwgKGByZXRoaW5raW5nYCkKCk5vdywgd2UgY2FuIHJ1biB0aGUgc2FtZSBtb2RlbCwgYnV0IHdpdGggYSBCYXllc2lhbiBhcHByb2FjaC4gIFRoaXMgcHJhY3RpY2Ugb2Ygc2V0dGluZyBwcmlvcnMgZm9yIHRoaXMga2luZCBvZiBtb2RlbCBnZXRzIF9yZWFsbHlfIHdlaXJkLCBzbyBpdCdzIHdvcnRoIGhhdmluZyBhIGxvb2sgYXQgQ2guIDE0IGluIHRoZSBNY0VscmF0aCB0ZXh0Ym9vayB0byBidWlsZCBzb21lIGludHVpdGlvbiBhcm91bmQgdGhlc2UuICBJIG1hZGUgbXkgYmVzdCBhdHRlbXB0IHRvIGRlc2NyaWJlIHdoYXQgSSB0aGluayBlYWNoIHByaW9yIGlzIGRvaW5nIGluIHRoZSBjb21tZW50cyBiZWxvdywgYnV0IEkgZG8gbm90IGNsYWltIHRvIGJlIGVub3VnaCBvZiBhICJtYXRoIHBlcnNvbiIgdG8gaGF2ZSB0b3RhbGx5IGZpZ3VyZWQgdGhlc2Ugb3V0OgoKYGBge3IgbW9kZWxfdmFyX2ludF92YXJfc2xvcGVfYmF5ZXN9CmZpdF9tb2RlbDNfYmF5ZXMgPC0gZnVuY3Rpb24oKSB7CgogICMgUHJlcCB0aGUgZGF0YSBjb2x1bW5zIHdlIHdhbnQgdG8gcGFzcyB0aGUgdGhlIG1vZGVsCiAgZGF0X2xpc3QgPC0gbGlzdCgKICAgIENPTVBfUkFURV9TVE5EX0hPVVJMWSA9IGhyXzIwMTlfdG9wXzI1X2pvYl9jbGFzc2VzJENPTVBfUkFURV9TVE5EX0hPVVJMWSwKICAgIEpPQl9JTkRFWCA9IGhyXzIwMTlfdG9wXzI1X2pvYl9jbGFzc2VzJEpPQl9JTkRFWCwKICAgIFlSU19TSU5DRV9PUklHSU5BTF9ISVJFID0gaHJfMjAxOV90b3BfMjVfam9iX2NsYXNzZXMkWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUsCiAgICBHRU5ERVJfSU5ERVggPSBocl8yMDE5X3RvcF8yNV9qb2JfY2xhc3NlcyRHRU5ERVJfSU5ERVgKICApCiAgCiAgdGljKCJSdW5uaW5nIG1vZGVsIDMgQmF5ZXNpYW4iKQogIG1vZGVsM19iYXllcyA8LSB1bGFtKAogICAgYWxpc3QoCiAgICAgIENPTVBfUkFURV9TVE5EX0hPVVJMWSB+IGRub3JtKCBtdSAsIHNpZ21hICksCiAgICAgIG11IDwtIEludGVyY2VwdCArIGFfam9iW0pPQl9JTkRFWF0gKyBhX2dlbmRlcltHRU5ERVJfSU5ERVhdICsgKGJfWVJTX1NJTkNFX09SSUdJTkFMX0hJUkUgKyBiX2dlbmRlcltHRU5ERVJfSU5ERVhdKSpZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRSwKICAgICAgCiAgICAgICMgcG9wdWxhdGlvbiBwcmlvcnMKICAgICAgSW50ZXJjZXB0IH4gZG5vcm0oMzAsIDEwKSwKICAgICAgc2lnbWEgfiBkY2F1Y2h5KDAsIDMwKSwKICAgICAgCiAgICAgICMgZml4ZWQgZWZmZWN0cyBwcmlvcnMKICAgICAgYl9ZUlNfU0lOQ0VfT1JJR0lOQUxfSElSRSB+IGRub3JtKDAuNjc1LCAxLjUpLAogICAgICAKICAgICAgIyB2YXJ5aW5nIGludGVyY2VwdHMgcHJpb3JzCiAgICAgIGFfam9iW0pPQl9JTkRFWF0gfiBkbm9ybSgwLCBhX2pvYl9zaWdtYSksICMgcHJpb3IgZm9yIG1lYW4gaG91cmx5IHN0YXJ0aW5nIHNhbGFyeSBvZiBlYWNoIGdyb3VwIChhZnRlciBhY2NvdW50aW5nIGZvciB0aGUgImdyYW5kIGludGVyY2VwdCIgZm9yIHRoZSBwb3B1bGF0aW9uIGFzIGEgd2hvbGUpOiBhIG5vcm1hbCBkaXN0cmlidXRpb24gY2VudGVyZWQgYXJvdW5kIDAKICAgICAgYV9qb2Jfc2lnbWEgfiBkY2F1Y2h5KDAsIDUwKSwgIyBwcmlvciBmb3IgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgbWVhbiBob3VybHkgc3RhcnRpbmcgc2FsYXJ5IGFjcm9zcyBncm91cHMKICAgICAgCiAgICAgICMgdmFyeWluZyBpbnRlcmNlcHRzICsgc2xvcGVzIHByaW9ycwogICAgICBjKGFfZ2VuZGVyLCBiX2dlbmRlcilbR0VOREVSX0lOREVYXSB+IG11bHRpX25vcm1hbCgwLCBSaG8sIHNpZ21hX2dlbmRlciksICMgd2UgdGhpbmsgaW50ZXJjZXB0cyBhbmQgc2xvcGVzIGFyZSBkaXN0cmlidXRlZCBhbG9uZyBhIG11bHRpdmFyaWF0ZSBub3JtYWwgZGlzdHJpYnV0aW9uIGRlc2NyaWJlZCBieSB0aGVpciBjb3JyZWxhdGlvbiAoUmhvKSBhbmQgc3RhbmRhcmQgZGV2aWF0aW9ucwogICAgICBSaG8gfiBkbGtqY29ycigyKSwgIyBwcmlvciBmb3IgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gc2xvcGVzIGFuZCBpbnRlcmNlcHRzIHdpdGhpbiBhIGdyb3VwIChzZWUgTWNFbHJhdGgsIHAuIDQ0MyBmb3IgYSBkaXNjdXNzaW9uIG9mIHRoaXMgcHJpb3IpCiAgICAgIHNpZ21hX2dlbmRlciB+IGV4cG9uZW50aWFsKDEpICMgPz8/CiAgICApLAogICAgZGF0YSA9IGRhdF9saXN0LCAKICAgIGNoYWlucyA9IDEsCiAgICBsb2dfbGlrID0gVFJVRQogICkKICAKICB0b2MoKSAjIHN0b3AgdGhlIHRpbWVyCiAgYmVlcHI6OmJlZXAoKSAjIGVtaXQgYSBiZWVwIHNvIHdlIGNhbiBjb21lIGJhY2sgZnJvbSB3aGF0ZXZlciBlbHNlIHdlIHdlcmUgZG9pbmcKICAKICBzYXZlUkRTKG1vZGVsM19iYXllcywgIi4vbW9kZWxzL21vZGVsM19iYXllcy5yZHMiKQogIHJldHVybihtb2RlbDNfYmF5ZXMpCn0KCmlmKGZpbGUuZXhpc3RzKCIuL21vZGVscy9tb2RlbDNfYmF5ZXMucmRzIikpIHsKICBjYXQoIk1vZGVsIGhhcyBhbHJlYWR5IGJlZW4gZml0ISAgTG9hZGluZyByZXN1bHRzIGZyb20gZGlzay4iKQogIG1vZGVsM19iYXllcyA8LSByZWFkUkRTKCIuL21vZGVscy9tb2RlbDNfYmF5ZXMucmRzIikKfSBlbHNlIHsgCiAgY2F0KCJNb2RlbCBoYXMgbm90IHlldCBiZWVuIGZpdC4gRml0dGluZyBtb2RlbCBub3cuIFBsZWFzZSBiZSBwYXRpZW50LiIpCiAgbW9kZWwzX2JheWVzIDwtIGZpdF9tb2RlbDNfYmF5ZXMoKQp9CmBgYAoKQW5kIHdlIGNhbiB2aWV3IHRoZSBtb2RlbCByZXN1bHRzOgoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFfQpwcmVjaXMobW9kZWwzX2JheWVzLCBkZXB0aD0zKQpwbG90KHByZWNpcyhtb2RlbDNfYmF5ZXMsIGRlcHRoPTIpLCBtYWluPSJBbGwgUG9zdGVyaW9yIFBhcmFtcyIpCnBsb3QocHJlY2lzKG1vZGVsM19iYXllcywgZGVwdGg9MiwgcGFycz0iYV9qb2IiKSwgbGFiZWxzPUpPQl9MQUJFTFMsIG1haW49IkpvYiBJbnRlcmNlcHRzIikKcGxvdChwcmVjaXMobW9kZWwzX2JheWVzLCBkZXB0aD0yLCBwYXJzPSJhX2dlbmRlciIpLCBsYWJlbHM9R0VOREVSX0xBQkVMUywgbWFpbj0iR2VuZGVyIEludGVyY2VwdHMiKQpwbG90KHByZWNpcyhtb2RlbDNfYmF5ZXMsIGRlcHRoPTIsIHBhcnM9ImJfZ2VuZGVyIiksIGxhYmVscz1HRU5ERVJfTEFCRUxTLCBtYWluPSJHZW5kZXIgU2xvcGVzIikKYGBgCgojIyMgQ29tcGFyZSB0aGUgQmF5ZXNpYW4gbW9kZWxzCgpXZSBjYW4gcnVuIGZpbmFsIGNvbXBhcmlzb24tLXRoaXMgdGltZSBhY3Jvc3MgYWxsIG9mIHRoZSBCYXllc2lhbiBtdWx0aWxldmVsIG1vZGVscyB3ZSd2ZSBmaXQgc28gZmFyLiAgV2UgY2FuIHNlZSB0aGF0IG1vZGVsIDMsIHdoaWNoIGNvbnRhaW5zIGpvYiBpbnRlcmNlcHRzICsgZ2VuZGVyIGludGVyY2VwdHMgYW5kIHNsb3BlcywgaGFzIGEgX3NsaWdodGx5XyBsb3dlciBXQUlDIHZhbHVlLiAgVGhpcyBkb2Vzbid0LCBob3dldmVyLCBzZWVtIHRvIGJlIHN1YnN0YW50aWFsIGZvciB1cyB0byBjb25jbHVkZSB0aGF0IHRoaXMgIm1lc3N5IiBnZW5kZXIgdmFyaWFibGUgYWRkcyBhIHN0cm9uZyBjb250cmlidXRpb24gdG8gdGhlIG1vZGVsLgoKYGBge3IgY29tcGFyZV9iYXllc19tb2RlbHN9CmNvbXBhcmUobW9kZWwyX2JheWVzLCBtb2RlbDJfYmF5ZXNfZXh0cmVtZV9wcmlvcnMsIG1vZGVsM19iYXllcykKYGBgCgoKIyMgQ29uY2x1c2lvbgoKSSBob3BlIHRoaXMgaGFzIHNlcnZlZCBhcyBhIHVzZWZ1bCBjcmFzaCBjb3Vyc2UgaW4gdGhlIGJhc2ljIHByaW5jaXBsZXMgYmVoaW5kIEJheWVzaWFuIHJlZ3Jlc3Npb24gYW5kIG11bHRpbGV2ZWwgbW9kZWxpbmcgZm9yIHRoaXMga2luZCBvZiBoaWVyYXJjaGljYWwgcmVncmVzc2lvbiB0YXNrLiAgQW5kIGFib3ZlIGFsbCwgdGhlIGludHVpdGlvbiBiZWhpbmQgInBvb2xpbmciIGFuZCBob3cgeW91IGNhbiBsZXZlcmFnZSBpdCB0byB5b3VyIGFkdmFudGFnZSBzaG91bGQgYmUgYSBrZXkgdGFrZS1hd2F5IG9mIG11bHRpbGV2ZWwgbW9kZWxpbmcuICBZb3UgbWF5IGZpbmQgaXQgdXNlZnVsIHRvIHN0YXJ0IGNvbnNpZGVyaW5nIHdoZXRoZXIgbXVsdGlsZXZlbCBzaG91bGQgYmVjb21lIHlvdXIgZGVmYXVsdCBjaG9pY2UtLXBhcnRpY3VsYXJseSB3aGVuIGZhY2VkIHdpdGggbW9kZWxpbmcgc2l0dWF0aW9ucyB0aGF0IGludm9sdmUgbmVzdGVkIG9yIGhpZXJhcmNoaWNhbCBkYXRhLiAgTGV0J3Mgc3VtIHRoaW5ncyB1cCB3aXRoIGEgcGl0eSBxdW90ZSBmcm9tIF9TdGF0aXN0aWNhbCBSZXRoaW5raW5nXzoKCj4gIkV2ZXJ5IG1vZGVsIGlzIGEgbWVyZ2VyIG9mIHNlbnNlIGFuZCBub25zZW5zZS4gV2hlbiB3ZSB1bmRlcnN0YW5kIGEgbW9kZWwsIHdlIGNhbiBmaW5kIGl0cyBzZW5zZSBhbmQgY29udHJvbCBpdHMgbm9uc2Vuc2UuIiB+IE1jRWxyZWF0aCwgcC4gNDI2CgoKCgo=