For this practical, we will use data from a sample of the Teaching
dataset of the Health Survey for England (2003-2005), which is available
here.
We will be fitting a series of binary logistic multilevel models for
the probability of being overweight or obese.
The traditional measure used to determine whether an adult is
overweight is the Body-Mass Index (BMI).
For an adult to be considered overweight, their BMI must be between
25 and 30. Meanwhile values over 30 are considered as “obese”.
Typical workflow setup and data preparation
1.1. Define a working directory
You can use any directory in your computer. As in the example
below:
setwd("C:/myfolder")
Remember to download the data to the folder you will define as
working directory, as this makes matters easier.
1.2. Load packages
You can always load packages later on, but it is a good practice to
load packages at the beginning of the session on the top of your script
or R markdown file.
In this practical, we will use the packages haven
,
lme4
and ggplot2
. Remember that if you haven’t
installed them before, you need to do so before you call the
library
function:
install.packages("tidyverse")
install.packages("haven")
install.packages("lme4")
install.packages("ggplot2")
install.packages("dplyr")
install.packages("broom.mixed")
Then you load them as such:
library(haven)
library(lme4)
library(ggplot2)
library(dplyr)
library(tidyverse)
library(broom.mixed)
1.3. Read in data
You can download the data from the UKDS
website. There are four files in different formats. We will
be using the Stata dataset named: “hse_data_for_workshop.dta”.
You can also download the file from the link below, but this link
will stop working by the end of the session, so make sure to register
and download the data from the UKDS if you want to continue working with
this dataset.
To read this dataset into R, we need to use the package
haven
:
hse <- read_dta("https://github.com/A-mora/MLM_summer-school/raw/main/data/hse_data_for_workshop.dta")
Select variables to use
The dataset has many different variables, but we’re not going to be
using all of them, so for easier work, we will select the following:
year
, pserial
, area
,
bmival
, sex
, age
and
ethnic
.
year
is the year where the measures were taken
pserial
is the anonymised person identifier
area
is the anonymised postcode of the person
bmival
is the valid measure of BMI
sex
coded 0 for Men and 1 for Women
age
from 0 to 99
ethnic
coded 1 for White; 2 for Mixed; 3 for Asian or
Asian British; 4 for Black or Black British and 5 for Chinese or any
other group
For a detailed description of these variables, have a look in the
documentation downloaded alongside with the data. Spoiler
alert: You will also find a complete set of materials for a
workshop on MLM.
To select variables, we (unsurprisingly) need to use the function
select
of the dplyr
package:
hse2 <- select(hse, year, pserial, area, bmival, sex, age, ethnic, bmival)
Preliminary tasks
We need to dichotomise our dependent variable bmival
. As
we mentioned before, the categories “overweight” and “obese” are for
those with a BMI over 25 and 30, respectively, so we need to do the
following to our continuous BMI measure:
hse2$bmi_bin <- ifelse(hse2$bmival >= 25, 1, 0)
To check this is done correctly, you can run a simple frequency
table:
table(hse2$bmi_bin)
##
## 0 1
## 14751 15873
If there are more than two values there, then something must have
gone wrong.
Also, given that the BMI in children is done differently, we will
remove all under-18 individuals. We do this by typing:
hse2 <- hse2 %>% filter(age>18)
Task 1: Fit an empty model
We will start by fitting an empty multilevel model for the propensity
of being overweight in people nested in areas.
empty <- glmer(bmi_bin ~ 1 + (1|area), data = hse2, family = binomial("logit"))
summary(empty)
## Generalized linear mixed model fit by maximum likelihood (Laplace
## Approximation) [glmerMod]
## Family: binomial ( logit )
## Formula: bmi_bin ~ 1 + (1 | area)
## Data: hse2
##
## AIC BIC logLik deviance df.resid
## 31279.3 31295.5 -15637.6 31275.3 23895
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -1.6139 -1.2694 0.7115 0.7547 0.9025
##
## Random effects:
## Groups Name Variance Std.Dev.
## area (Intercept) 0.06817 0.2611
## Number of obs: 23897, groups: area, 1833
##
## Fixed effects:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 0.5641 0.0153 36.87 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Questions:
1.1 What is the overall probability of being overweight?
Remember this formula:
\[logit(p_i)=\log\left(\frac{p_{i}}{1-p_{i}}\right)\]
To get the odds from the estimated coefficients, you use the exponent
function:
\[exp(\beta)=odds\]
Then, to convert to probability, you can do this:
\[p = \frac{odds}{1 + odds}\]
odds <- exp(empty@beta) # this is to retrieve the intercept from the model
prob <- odds/(1 + odds) # to estimate the overall probability
prob # to print the result to the screen
## [1] 0.6374097
1.2 What is the VPC for this model?
\[VPC = \frac{\sigma_u^2}{\sigma_u^2 +
\sigma_e^2*}\] where \(\sigma_e^2*=
\frac{\pi^2}{3} \approx 3.29\)
lev2var <- as.numeric(VarCorr(empty)) # this is to retrieve the level 2 variance from the model
(lev2var)/(lev2var+3.29) # VPC
## [1] 0.02029849
Task 2: Is the MLM better than a single-level model?
To assess the statistical significance of the MLM, we need to compare
it to a single-level model. To fit a single-level model, we type:
single <- glm(bmi_bin ~ 1 , data = hse2, family = binomial("logit"))
summary(single)
##
## Call:
## glm(formula = bmi_bin ~ 1, family = binomial("logit"), data = hse2)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -1.4234 -1.4234 0.9499 0.9499 0.9499
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 0.56193 0.01345 41.77 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 31314 on 23896 degrees of freedom
## Residual deviance: 31314 on 23896 degrees of freedom
## (4034 observations deleted due to missingness)
## AIC: 31316
##
## Number of Fisher Scoring iterations: 4
These results are not very interesting in themselves, so we move on
extract the loglikelihood and compare to the MLM.
L1 <- as.numeric(logLik(single)) # store this as numerical to re-use
L2 <- as.numeric(logLik(empty))
(D <- 2*(L2-L1)) # this is the deviance, we put it within brackets to print it immediately
## [1] 38.42078
pchisq(q=D, df=1, lower.tail=F) # To find the p-value
## [1] 5.702211e-10
Question
2.1. Is the addition of the area level statistically significant?
Answer: Yes, it is. The likelihood ratio test shows
that the multilevel model fits the data better than the single-level
model
2.2. What does this mean in practice?
Answer: This means that there are differences in the
propensity towards being overweight that are not attributable to the
individuals, but to (unknown) environmental factors of the areas where
they live. Statistically speaking, this needs to be controlled for to be
able to draw correct inferences.
Task 3: Random intercepts model
Just like in the case of normally-distributed responses, we can fit a
random intercepts
model by adding explanatory
variables.
We want to find out whether there any differences between males and
females on the probability of being overweight.
rand_int <- glmer(bmi_bin ~ factor(sex) + (1|area), data = hse2, family = binomial("logit"))
summary(rand_int)
## Generalized linear mixed model fit by maximum likelihood (Laplace
## Approximation) [glmerMod]
## Family: binomial ( logit )
## Formula: bmi_bin ~ factor(sex) + (1 | area)
## Data: hse2
##
## AIC BIC logLik deviance df.resid
## 30962.2 30986.4 -15478.1 30956.2 23894
##
## Scaled residuals:
## Min 1Q Median 3Q Max
## -1.9006 -1.1735 0.6406 0.7871 1.0249
##
## Random effects:
## Groups Name Variance Std.Dev.
## area (Intercept) 0.07516 0.2742
## Number of obs: 23897, groups: area, 1833
##
## Fixed effects:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 0.84265 0.02246 37.52 <2e-16 ***
## factor(sex)1 -0.49290 0.02781 -17.72 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Correlation of Fixed Effects:
## (Intr)
## factor(sx)1 -0.722
We can also get a more “human-readable” version of the table of
coefficients by using the package broom.mixed
:
(m1 <- tidy(rand_int, effects = "fixed",
conf.int=TRUE,
exponentiate=FALSE))
## # A tibble: 2 × 8
## effect term estimate std.error statistic p.value conf.low conf.high
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 fixed (Intercept) 0.843 0.0225 37.5 0 0.799 0.887
## 2 fixed factor(sex)1 -0.493 0.0278 -17.7 2.78e-70 -0.547 -0.438
You can plot these for easier inspection:
(m1_plot <-
ggplot(m1,
mapping = aes(x= estimate,
y = term,
xmin = conf.low,
xmax = conf.high)) +
geom_vline(xintercept = 0, color = "tomato", size =1) +
geom_pointrange() +
theme_classic())
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.

If you want to obtain the odds ratios, you can change the option
exponentiate
to TRUE
.
(m1_odds <- tidy(rand_int, effects = "fixed",
conf.int=TRUE,
exponentiate=TRUE))
## # A tibble: 2 × 8
## effect term estimate std.error statistic p.value conf.low conf.high
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 fixed (Intercept) 2.32 0.0522 37.5 0 2.22 2.43
## 2 fixed factor(sex)1 0.611 0.0170 -17.7 2.78e-70 0.578 0.645
Unsurprisingly, you can plot the odds for easier inspection:
(m1_odds_plot <-
ggplot(m1_odds,
mapping = aes(x= estimate,
y = term,
xmin = conf.low,
xmax = conf.high)) +
geom_vline(xintercept = 1, color = "tomato", size =1) +
geom_pointrange() +
theme_classic())

Question:
3.1. What is the effect of sex on being overweight?
Answer: Women are expected to be significantly less
likely than men to be overweight.
3.2. What is the probability of a female being overweight in this
sample?
Answer: Similar to the steps from question 1.1, but
this time we need to do the following:
\[exp(\beta_0 + \beta_1)=odds\]
\[exp(0.84265 - 0.4929) = 1.419\] And
then:
\[p = \frac{1.419}{1 +
1.419}=0.587\]
Women in this sample have an expected probability of 0.587 of being
overweight.
Bonus
You can also obtain expected probabilities, by using marginal
effects. Have a look at the “marginaleffects” package on this link.
Task 4: Area-specific estimates
Just like in task 5 of practical 2, we can retrieve the residuals
from the random intercepts
and plot them to find out which
areas have more or less than average people who are overweight.
This is what is sometimes called in the literature a “Caterpillar
plot”.
m2 <- tidy(rand_int, effects = "ran_vals",
conf.int=TRUE,
exponentiate=FALSE)
(RE <- m2 %>%
mutate(level = fct_reorder(level, estimate)) %>%
ggplot( mapping = aes(x= estimate,
y = level,
xmin = conf.low,
xmax = conf.high)) +
geom_vline(xintercept = 0, color = "tomato", size =1) +
coord_flip() +
labs(x = "logit",
y = "Areas") +
geom_pointrange() +
theme_classic())

Question:
4.1. Are there any areas that are significantly above or below
average?
Answer: Even though the plot may be hard to inspect
closely, it seems as if all areas overlap with zero (national average).
This would mean that having controlled for sex, no areas are
significantly below or above average.
Additional tasks
You can borrow from practical 2 and repeat some of the tasks there,
such as:
LS0tDQp0aXRsZTogIkludHJvIHRvIE1MTTogUHJhY3RpY2FsIDMiDQphdXRob3I6IEFuYSBNb3JhbGVzLUfDs21leiBhbmQgUGF0cmljaW8gVHJvbmNvc28gDQpkYXRlOiAiSnVuZSAyMDI0Ig0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBoaWdobGlnaHRlcjogbnVsbA0KICAgIHRoZW1lOiBjb3Ntbw0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBmb250c2l6ZTogMTJwdA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCg0KRm9yIHRoaXMgcHJhY3RpY2FsLCB3ZSB3aWxsIHVzZSBkYXRhIGZyb20gYSBzYW1wbGUgb2YgdGhlIFRlYWNoaW5nIGRhdGFzZXQgb2YgdGhlIEhlYWx0aCBTdXJ2ZXkgZm9yIEVuZ2xhbmQgKDIwMDMtMjAwNSksIHdoaWNoIGlzDQphdmFpbGFibGUgWyoqaGVyZSoqXShodHRwczovL2JldGEudWtkYXRhc2VydmljZS5hYy51ay9kYXRhY2F0YWxvZ3VlL3N0dWRpZXMvc3R1ZHk/aWQ9Njc2NSkuDQoNCldlIHdpbGwgYmUgZml0dGluZyBhIHNlcmllcyBvZiBiaW5hcnkgbG9naXN0aWMgbXVsdGlsZXZlbCBtb2RlbHMgZm9yIHRoZSBwcm9iYWJpbGl0eSBvZiBiZWluZyBvdmVyd2VpZ2h0IG9yIG9iZXNlLg0KDQpUaGUgdHJhZGl0aW9uYWwgbWVhc3VyZSB1c2VkIHRvIGRldGVybWluZSB3aGV0aGVyIGFuIGFkdWx0IGlzIG92ZXJ3ZWlnaHQgaXMgdGhlIEJvZHktTWFzcyBJbmRleCAoQk1JKS4NCg0KRm9yIGFuIGFkdWx0IHRvIGJlIGNvbnNpZGVyZWQgb3ZlcndlaWdodCwgdGhlaXIgQk1JIG11c3QgYmUgYmV0d2VlbiAyNSBhbmQgMzAuIE1lYW53aGlsZSB2YWx1ZXMgb3ZlciAzMCBhcmUgY29uc2lkZXJlZCBhcyAib2Jlc2UiLg0KDQo8YnI+DQoNCioqKg0KDQojIFR5cGljYWwgd29ya2Zsb3cgc2V0dXAgYW5kIGRhdGEgcHJlcGFyYXRpb24NCg0KIyMgMS4xLiBEZWZpbmUgYSB3b3JraW5nIGRpcmVjdG9yeQ0KDQpZb3UgY2FuIHVzZSBhbnkgZGlyZWN0b3J5IGluIHlvdXIgY29tcHV0ZXIuIEFzIGluIHRoZSBleGFtcGxlIGJlbG93Og0KDQpgYGB7ciwgZXZhbD1GfQ0Kc2V0d2QoIkM6L215Zm9sZGVyIikNCmBgYA0KDQpSZW1lbWJlciB0byBkb3dubG9hZCB0aGUgZGF0YSB0byB0aGUgZm9sZGVyIHlvdSB3aWxsIGRlZmluZSBhcyB3b3JraW5nIGRpcmVjdG9yeSwgYXMgdGhpcyBtYWtlcyBtYXR0ZXJzIGVhc2llci4NCg0KIyMgMS4yLiBMb2FkIHBhY2thZ2VzDQoNCllvdSBjYW4gYWx3YXlzIGxvYWQgcGFja2FnZXMgbGF0ZXIgb24sIGJ1dCBpdCBpcyBhIGdvb2QgcHJhY3RpY2UgdG8gbG9hZCBwYWNrYWdlcyBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZSBzZXNzaW9uIG9uIHRoZSB0b3Agb2YgeW91ciBzY3JpcHQgb3IgUiBtYXJrZG93biBmaWxlLg0KDQpJbiB0aGlzIHByYWN0aWNhbCwgd2Ugd2lsbCB1c2UgdGhlIHBhY2thZ2VzIGBoYXZlbmAsIGBsbWU0YCBhbmQgYGdncGxvdDJgLiBSZW1lbWJlciB0aGF0IGlmIHlvdSBoYXZlbid0IGluc3RhbGxlZCB0aGVtIGJlZm9yZSwgeW91IG5lZWQgdG8gZG8gc28gYmVmb3JlIHlvdSBjYWxsIHRoZSBgbGlicmFyeWAgZnVuY3Rpb246DQoNCmBgYHt9DQppbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KaW5zdGFsbC5wYWNrYWdlcygiaGF2ZW4iKQ0KaW5zdGFsbC5wYWNrYWdlcygibG1lNCIpDQppbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmluc3RhbGwucGFja2FnZXMoImRwbHlyIikNCmluc3RhbGwucGFja2FnZXMoImJyb29tLm1peGVkIikNCmBgYA0KDQpUaGVuIHlvdSBsb2FkIHRoZW0gYXMgc3VjaDoNCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KbGlicmFyeShoYXZlbikNCmxpYnJhcnkobG1lNCkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoYnJvb20ubWl4ZWQpDQpgYGANCg0KIyMgMS4zLiBSZWFkIGluIGRhdGEgDQoNCllvdSBjYW4gZG93bmxvYWQgdGhlIGRhdGEgZnJvbSAgdGhlIFsqKlVLRFMgd2Vic2l0ZSoqXShodHRwczovL2JldGEudWtkYXRhc2VydmljZS5hYy51ay9kYXRhY2F0YWxvZ3VlL3N0dWRpZXMvc3R1ZHk/aWQ9Njc2NSkuIFRoZXJlIGFyZSBmb3VyIGZpbGVzIGluIGRpZmZlcmVudCBmb3JtYXRzLiBXZSB3aWxsIGJlIHVzaW5nIHRoZSBTdGF0YSBkYXRhc2V0IG5hbWVkOiAiaHNlX2RhdGFfZm9yX3dvcmtzaG9wLmR0YSIuDQoNCllvdSBjYW4gYWxzbyBkb3dubG9hZCB0aGUgZmlsZSBmcm9tIHRoZSBsaW5rIGJlbG93LCBidXQgdGhpcyBsaW5rIHdpbGwgc3RvcCB3b3JraW5nIGJ5IHRoZSBlbmQgb2YgdGhlIHNlc3Npb24sIHNvIG1ha2Ugc3VyZSB0byByZWdpc3RlciBhbmQgZG93bmxvYWQgdGhlIGRhdGEgZnJvbSB0aGUgVUtEUyBpZiB5b3Ugd2FudCB0byBjb250aW51ZSB3b3JraW5nIHdpdGggdGhpcyBkYXRhc2V0Lg0KDQpUbyByZWFkIHRoaXMgZGF0YXNldCBpbnRvIFIsIHdlIG5lZWQgdG8gdXNlIHRoZSBwYWNrYWdlIGBoYXZlbmA6DQoNCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KaHNlIDwtIHJlYWRfZHRhKCJodHRwczovL2dpdGh1Yi5jb20vQS1tb3JhL01MTV9zdW1tZXItc2Nob29sL3Jhdy9tYWluL2RhdGEvaHNlX2RhdGFfZm9yX3dvcmtzaG9wLmR0YSIpDQpgYGANCg0KPGJyPg0KDQoqKioNCg0KIyMgU2VsZWN0IHZhcmlhYmxlcyB0byB1c2UNCg0KVGhlIGRhdGFzZXQgaGFzIG1hbnkgZGlmZmVyZW50IHZhcmlhYmxlcywgYnV0IHdlJ3JlIG5vdCBnb2luZyB0byBiZSB1c2luZyBhbGwgb2YgdGhlbSwgc28gZm9yIGVhc2llciB3b3JrLCB3ZSB3aWxsIHNlbGVjdCB0aGUgZm9sbG93aW5nOiBgeWVhcmAsIGBwc2VyaWFsYCwgYGFyZWFgLCBgYm1pdmFsYCwgYHNleGAsIGBhZ2VgIGFuZCBgZXRobmljYC4NCg0KYHllYXJgIGlzIHRoZSB5ZWFyIHdoZXJlIHRoZSBtZWFzdXJlcyB3ZXJlIHRha2VuDQoNCmBwc2VyaWFsYCBpcyB0aGUgYW5vbnltaXNlZCBwZXJzb24gaWRlbnRpZmllcg0KDQpgYXJlYWAgaXMgdGhlIGFub255bWlzZWQgcG9zdGNvZGUgb2YgdGhlIHBlcnNvbg0KDQpgYm1pdmFsYCBpcyB0aGUgdmFsaWQgbWVhc3VyZSBvZiBCTUkNCg0KYHNleGAgY29kZWQgMCBmb3IgTWVuIGFuZCAxIGZvciBXb21lbg0KDQpgYWdlYCBmcm9tIDAgdG8gOTkNCg0KYGV0aG5pY2AgY29kZWQgMSBmb3IgV2hpdGU7IA0KICAgICAgICAgICAgICAgMiBmb3IgTWl4ZWQ7IA0KICAgICAgICAgICAgICAgMyBmb3IgQXNpYW4gb3IgQXNpYW4gQnJpdGlzaDsgDQogICAgICAgICAgICAgICA0IGZvciBCbGFjayBvciBCbGFjayBCcml0aXNoIGFuZCANCiAgICAgICAgICAgICAgIDUgZm9yIENoaW5lc2Ugb3IgYW55IG90aGVyIGdyb3VwDQoNCkZvciBhIGRldGFpbGVkIGRlc2NyaXB0aW9uIG9mIHRoZXNlIHZhcmlhYmxlcywgaGF2ZSBhIGxvb2sgaW4gdGhlIGRvY3VtZW50YXRpb24gZG93bmxvYWRlZCBhbG9uZ3NpZGUgd2l0aCB0aGUgZGF0YS4gDQoqKlNwb2lsZXIgYWxlcnQ6KiogWW91IHdpbGwgYWxzbyBmaW5kIGEgY29tcGxldGUgc2V0IG9mIG1hdGVyaWFscyBmb3IgYSB3b3Jrc2hvcCBvbiBNTE0uDQoNClRvIHNlbGVjdCB2YXJpYWJsZXMsIHdlICh1bnN1cnByaXNpbmdseSkgbmVlZCB0byB1c2UgdGhlIGZ1bmN0aW9uIGBzZWxlY3RgIG9mIHRoZSBgZHBseXJgIHBhY2thZ2U6DQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0NCg0KaHNlMiA8LSBzZWxlY3QoaHNlLCB5ZWFyLCBwc2VyaWFsLCBhcmVhLCBibWl2YWwsIHNleCwgYWdlLCBldGhuaWMsIGJtaXZhbCkgDQoNCmBgYA0KDQo8YnI+DQoNCioqKg0KIyBQcmVsaW1pbmFyeSB0YXNrcw0KDQpXZSBuZWVkIHRvIGRpY2hvdG9taXNlIG91ciBkZXBlbmRlbnQgdmFyaWFibGUgYGJtaXZhbGAuIEFzIHdlIG1lbnRpb25lZCBiZWZvcmUsIHRoZSBjYXRlZ29yaWVzICJvdmVyd2VpZ2h0IiBhbmQgIm9iZXNlIiBhcmUgZm9yIHRob3NlIHdpdGggYSBCTUkgb3ZlciAyNSBhbmQgMzAsIHJlc3BlY3RpdmVseSwgc28gd2UgbmVlZCB0byBkbyB0aGUgZm9sbG93aW5nIHRvIG91ciBjb250aW51b3VzIEJNSSBtZWFzdXJlOg0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQpoc2UyJGJtaV9iaW4gPC0gaWZlbHNlKGhzZTIkYm1pdmFsID49IDI1LCAxLCAwKQ0KYGBgDQoNClRvIGNoZWNrIHRoaXMgaXMgZG9uZSBjb3JyZWN0bHksIHlvdSBjYW4gcnVuIGEgc2ltcGxlIGZyZXF1ZW5jeSB0YWJsZToNCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KdGFibGUoaHNlMiRibWlfYmluKQ0KYGBgDQoNCklmIHRoZXJlIGFyZSBtb3JlIHRoYW4gdHdvIHZhbHVlcyB0aGVyZSwgdGhlbiBzb21ldGhpbmcgbXVzdCBoYXZlIGdvbmUgd3JvbmcuDQoNCkFsc28sIGdpdmVuIHRoYXQgdGhlIEJNSSBpbiBjaGlsZHJlbiBpcyBkb25lIGRpZmZlcmVudGx5LCB3ZSB3aWxsIHJlbW92ZSBhbGwgdW5kZXItMTggaW5kaXZpZHVhbHMuIFdlIGRvIHRoaXMgYnkgdHlwaW5nOg0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQpoc2UyIDwtIGhzZTIgJT4lIGZpbHRlcihhZ2U+MTgpDQpgYGANCg0KIyBUYXNrIDE6IEZpdCBhbiBlbXB0eSBtb2RlbA0KDQpXZSB3aWxsIHN0YXJ0IGJ5IGZpdHRpbmcgYW4gZW1wdHkgbXVsdGlsZXZlbCBtb2RlbCBmb3IgdGhlIHByb3BlbnNpdHkgb2YgYmVpbmcgb3ZlcndlaWdodCBpbiBwZW9wbGUgbmVzdGVkIGluIGFyZWFzLg0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQplbXB0eSA8LSBnbG1lcihibWlfYmluIH4gMSArICgxfGFyZWEpLCBkYXRhID0gaHNlMiwgZmFtaWx5ID0gYmlub21pYWwoImxvZ2l0IikpDQoNCnN1bW1hcnkoZW1wdHkpDQpgYGANCg0KDQo8YnI+DQoNCiMjIyBRdWVzdGlvbnM6DQoNCjEuMSBXaGF0IGlzIHRoZSBvdmVyYWxsIHByb2JhYmlsaXR5IG9mIGJlaW5nIG92ZXJ3ZWlnaHQ/DQoNClJlbWVtYmVyIHRoaXMgZm9ybXVsYToNCg0KJCRsb2dpdChwX2kpPVxsb2dcbGVmdChcZnJhY3twX3tpfX17MS1wX3tpfX1ccmlnaHQpJCQNClRvIGdldCB0aGUgb2RkcyBmcm9tIHRoZSBlc3RpbWF0ZWQgY29lZmZpY2llbnRzLCB5b3UgdXNlIHRoZSBleHBvbmVudCBmdW5jdGlvbjoNCg0KJCRleHAoXGJldGEpPW9kZHMkJA0KDQpUaGVuLCB0byBjb252ZXJ0IHRvIHByb2JhYmlsaXR5LCB5b3UgY2FuIGRvIHRoaXM6DQoNCiQkcCA9IFxmcmFje29kZHN9ezEgKyBvZGRzfSQkDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQoNCm9kZHMgPC0gZXhwKGVtcHR5QGJldGEpICMgdGhpcyBpcyB0byByZXRyaWV2ZSB0aGUgaW50ZXJjZXB0IGZyb20gdGhlIG1vZGVsDQoNCnByb2IgPC0gb2Rkcy8oMSArIG9kZHMpICMgdG8gZXN0aW1hdGUgdGhlIG92ZXJhbGwgcHJvYmFiaWxpdHkNCg0KcHJvYiAjIHRvIHByaW50IHRoZSByZXN1bHQgdG8gdGhlIHNjcmVlbg0KYGBgDQoNCg0KMS4yIFdoYXQgaXMgdGhlIFZQQyBmb3IgdGhpcyBtb2RlbD8NCg0KJCRWUEMgPSBcZnJhY3tcc2lnbWFfdV4yfXtcc2lnbWFfdV4yICsgXHNpZ21hX2VeMip9JCQNCndoZXJlICRcc2lnbWFfZV4yKj0gXGZyYWN7XHBpXjJ9ezN9IFxhcHByb3ggMy4yOSQNCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KbGV2MnZhciA8LSBhcy5udW1lcmljKFZhckNvcnIoZW1wdHkpKSAjIHRoaXMgaXMgdG8gcmV0cmlldmUgdGhlIGxldmVsIDIgdmFyaWFuY2UgZnJvbSB0aGUgbW9kZWwNCg0KKGxldjJ2YXIpLyhsZXYydmFyKzMuMjkpICMgVlBDDQpgYGANCg0KPGJyPg0KDQoqKioNCg0KIyBUYXNrIDI6IElzIHRoZSBNTE0gYmV0dGVyIHRoYW4gYSBzaW5nbGUtbGV2ZWwgbW9kZWw/DQoNClRvIGFzc2VzcyB0aGUgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIG9mIHRoZSBNTE0sIHdlIG5lZWQgdG8gY29tcGFyZSBpdCB0byBhIHNpbmdsZS1sZXZlbCBtb2RlbC4gVG8gZml0IGEgc2luZ2xlLWxldmVsIG1vZGVsLCB3ZSB0eXBlOg0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQoNCnNpbmdsZSA8LSBnbG0oYm1pX2JpbiB+IDEgLCBkYXRhID0gaHNlMiwgZmFtaWx5ID0gYmlub21pYWwoImxvZ2l0IikpDQoNCnN1bW1hcnkoc2luZ2xlKQ0KDQpgYGANCg0KVGhlc2UgcmVzdWx0cyBhcmUgbm90IHZlcnkgaW50ZXJlc3RpbmcgaW4gdGhlbXNlbHZlcywgc28gd2UgbW92ZSBvbiBleHRyYWN0IHRoZSBsb2dsaWtlbGlob29kIGFuZCBjb21wYXJlIHRvIHRoZSBNTE0uDQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0NCg0KTDEgPC0gYXMubnVtZXJpYyhsb2dMaWsoc2luZ2xlKSkgIyBzdG9yZSB0aGlzIGFzIG51bWVyaWNhbCB0byByZS11c2UNCkwyIDwtIGFzLm51bWVyaWMobG9nTGlrKGVtcHR5KSkNCg0KKEQgPC0gMiooTDItTDEpKSAjIHRoaXMgaXMgdGhlIGRldmlhbmNlLCB3ZSBwdXQgaXQgd2l0aGluIGJyYWNrZXRzIHRvIHByaW50IGl0IGltbWVkaWF0ZWx5DQoNCnBjaGlzcShxPUQsIGRmPTEsIGxvd2VyLnRhaWw9RikgIyBUbyBmaW5kIHRoZSBwLXZhbHVlDQoNCmBgYA0KDQojIyMgUXVlc3Rpb24NCg0KMi4xLiBJcyB0aGUgYWRkaXRpb24gb2YgdGhlIGFyZWEgbGV2ZWwgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudD8NCg0KKipBbnN3ZXI6KiogWWVzLCBpdCBpcy4gVGhlIGxpa2VsaWhvb2QgcmF0aW8gdGVzdCBzaG93cyB0aGF0IHRoZSBtdWx0aWxldmVsIG1vZGVsIGZpdHMgdGhlIGRhdGEgYmV0dGVyIHRoYW4gdGhlIHNpbmdsZS1sZXZlbCBtb2RlbA0KDQoyLjIuIFdoYXQgZG9lcyB0aGlzIG1lYW4gaW4gcHJhY3RpY2U/DQoNCioqQW5zd2VyOioqIFRoaXMgbWVhbnMgdGhhdCB0aGVyZSBhcmUgZGlmZmVyZW5jZXMgaW4gdGhlIHByb3BlbnNpdHkgdG93YXJkcyBiZWluZyBvdmVyd2VpZ2h0IHRoYXQgYXJlIG5vdCBhdHRyaWJ1dGFibGUgdG8gdGhlIGluZGl2aWR1YWxzLCBidXQgdG8gKHVua25vd24pIGVudmlyb25tZW50YWwgZmFjdG9ycyBvZiB0aGUgYXJlYXMgd2hlcmUgdGhleSBsaXZlLiBTdGF0aXN0aWNhbGx5IHNwZWFraW5nLCB0aGlzIG5lZWRzIHRvIGJlIGNvbnRyb2xsZWQgZm9yIHRvIGJlIGFibGUgdG8gZHJhdyBjb3JyZWN0IGluZmVyZW5jZXMuDQoNCg0KKioqDQoNCiMgVGFzayAzOiBSYW5kb20gaW50ZXJjZXB0cyBtb2RlbA0KDQpKdXN0IGxpa2UgaW4gdGhlIGNhc2Ugb2Ygbm9ybWFsbHktZGlzdHJpYnV0ZWQgcmVzcG9uc2VzLCB3ZSBjYW4gZml0IGEgYHJhbmRvbSBpbnRlcmNlcHRzYCBtb2RlbCBieSBhZGRpbmcgZXhwbGFuYXRvcnkgdmFyaWFibGVzLg0KDQpXZSB3YW50IHRvIGZpbmQgb3V0IHdoZXRoZXIgdGhlcmUgYW55IGRpZmZlcmVuY2VzIGJldHdlZW4gbWFsZXMgYW5kIGZlbWFsZXMgb24gdGhlIHByb2JhYmlsaXR5IG9mIGJlaW5nIG92ZXJ3ZWlnaHQuDQoNCmBgYHtyLCB3YXJuaW5nPVQsIG1lc3NhZ2U9Rn0NCnJhbmRfaW50IDwtIGdsbWVyKGJtaV9iaW4gfiBmYWN0b3Ioc2V4KSArICgxfGFyZWEpLCBkYXRhID0gaHNlMiwgZmFtaWx5ID0gYmlub21pYWwoImxvZ2l0IikpDQoNCnN1bW1hcnkocmFuZF9pbnQpDQpgYGANCg0KV2UgY2FuIGFsc28gZ2V0IGEgbW9yZSAiaHVtYW4tcmVhZGFibGUiIHZlcnNpb24gb2YgdGhlIHRhYmxlIG9mIGNvZWZmaWNpZW50cyBieSB1c2luZyB0aGUgcGFja2FnZSBgYnJvb20ubWl4ZWRgOg0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQoobTEgPC0gdGlkeShyYW5kX2ludCwgZWZmZWN0cyA9ICJmaXhlZCIsDQogICAgICAgICAgY29uZi5pbnQ9VFJVRSwgDQogICAgICAgICAgZXhwb25lbnRpYXRlPUZBTFNFKSkNCmBgYA0KDQpZb3UgY2FuIHBsb3QgdGhlc2UgZm9yIGVhc2llciBpbnNwZWN0aW9uOg0KDQpgYGB7cn0NCihtMV9wbG90IDwtIA0KICAgIGdncGxvdChtMSwgDQogICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeD0gZXN0aW1hdGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHRlcm0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHhtaW4gPSBjb25mLmxvdywgDQogICAgICAgICAgICAgICAgICAgICAgICAgeG1heCA9IGNvbmYuaGlnaCkpICsNCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJ0b21hdG8iLCBzaXplID0xKSArDQogICAgZ2VvbV9wb2ludHJhbmdlKCkgKw0KICAgIHRoZW1lX2NsYXNzaWMoKSkNCmBgYA0KDQpJZiB5b3Ugd2FudCB0byBvYnRhaW4gdGhlIG9kZHMgcmF0aW9zLCB5b3UgY2FuIGNoYW5nZSB0aGUgb3B0aW9uIGBleHBvbmVudGlhdGVgIHRvIGBUUlVFYC4NCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KKG0xX29kZHMgPC0gdGlkeShyYW5kX2ludCwgZWZmZWN0cyA9ICJmaXhlZCIsDQogICAgICAgICAgY29uZi5pbnQ9VFJVRSwgDQogICAgICAgICAgZXhwb25lbnRpYXRlPVRSVUUpKQ0KYGBgDQoNClVuc3VycHJpc2luZ2x5LCB5b3UgY2FuIHBsb3QgdGhlIG9kZHMgZm9yIGVhc2llciBpbnNwZWN0aW9uOg0KDQpgYGB7cn0NCihtMV9vZGRzX3Bsb3QgPC0gDQogICAgZ2dwbG90KG0xX29kZHMsIA0KICAgICAgICAgICBtYXBwaW5nID0gYWVzKHg9IGVzdGltYXRlLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSB0ZXJtLCANCiAgICAgICAgICAgICAgICAgICAgICAgICB4bWluID0gY29uZi5sb3csIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHhtYXggPSBjb25mLmhpZ2gpKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMSwgY29sb3IgPSAidG9tYXRvIiwgc2l6ZSA9MSkgKw0KICAgIGdlb21fcG9pbnRyYW5nZSgpICsNCiAgICB0aGVtZV9jbGFzc2ljKCkpDQpgYGANCg0KIyMjIFF1ZXN0aW9uOg0KDQozLjEuIFdoYXQgaXMgdGhlIGVmZmVjdCBvZiBzZXggb24gYmVpbmcgb3ZlcndlaWdodD8NCg0KKipBbnN3ZXI6KiogV29tZW4gYXJlIGV4cGVjdGVkIHRvIGJlIHNpZ25pZmljYW50bHkgbGVzcyBsaWtlbHkgdGhhbiBtZW4gdG8gYmUgb3ZlcndlaWdodC4NCg0KMy4yLiBXaGF0IGlzIHRoZSBwcm9iYWJpbGl0eSBvZiBhIGZlbWFsZSBiZWluZyBvdmVyd2VpZ2h0IGluIHRoaXMgc2FtcGxlPw0KDQoqKkFuc3dlcjoqKiBTaW1pbGFyIHRvIHRoZSBzdGVwcyBmcm9tIHF1ZXN0aW9uIDEuMSwgYnV0IHRoaXMgdGltZSB3ZSBuZWVkIHRvIGRvIHRoZSBmb2xsb3dpbmc6DQoNCiQkZXhwKFxiZXRhXzAgKyBcYmV0YV8xKT1vZGRzJCQNCiQkZXhwKDAuODQyNjUgLSAwLjQ5MjkpID0gMS40MTkkJA0KQW5kIHRoZW46DQoNCiQkcCA9IFxmcmFjezEuNDE5fXsxICsgMS40MTl9PTAuNTg3JCQNCg0KV29tZW4gaW4gdGhpcyBzYW1wbGUgaGF2ZSBhbiBleHBlY3RlZCBwcm9iYWJpbGl0eSBvZiAwLjU4NyBvZiBiZWluZyBvdmVyd2VpZ2h0Lg0KDQojIyBCb251cw0KDQpZb3UgY2FuIGFsc28gb2J0YWluIGV4cGVjdGVkIHByb2JhYmlsaXRpZXMsIGJ5IHVzaW5nIG1hcmdpbmFsIGVmZmVjdHMuIEhhdmUgYSBsb29rIGF0IHRoZSAibWFyZ2luYWxlZmZlY3RzIiBwYWNrYWdlIG9uIHRoaXMgW2xpbmsuXShodHRwczovL3ZpbmNlbnRhcmVsYnVuZG9jay5naXRodWIuaW8vbWFyZ2luYWxlZmZlY3RzLykgDQoNCioqKg0KDQojIFRhc2sgNDogQXJlYS1zcGVjaWZpYyBlc3RpbWF0ZXMNCg0KSnVzdCBsaWtlIGluIHRhc2sgNSBvZiBwcmFjdGljYWwgMiwgd2UgY2FuIHJldHJpZXZlIHRoZSByZXNpZHVhbHMgZnJvbSB0aGUgYHJhbmRvbSBpbnRlcmNlcHRzYCBhbmQgcGxvdCB0aGVtIHRvIGZpbmQgb3V0IHdoaWNoIGFyZWFzIGhhdmUgbW9yZSBvciBsZXNzIHRoYW4gYXZlcmFnZSBwZW9wbGUgd2hvIGFyZSBvdmVyd2VpZ2h0Lg0KDQpUaGlzIGlzIHdoYXQgaXMgc29tZXRpbWVzIGNhbGxlZCBpbiB0aGUgbGl0ZXJhdHVyZSBhICJDYXRlcnBpbGxhciBwbG90Ii4NCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KbTIgPC0gdGlkeShyYW5kX2ludCwgZWZmZWN0cyA9ICJyYW5fdmFscyIsDQogICAgICAgICAgY29uZi5pbnQ9VFJVRSwNCiAgICAgICAgICBleHBvbmVudGlhdGU9RkFMU0UpDQoNCihSRSA8LSBtMiAlPiUNCiAgICBtdXRhdGUobGV2ZWwgPSBmY3RfcmVvcmRlcihsZXZlbCwgZXN0aW1hdGUpKSAlPiUNCiAgICBnZ3Bsb3QoIG1hcHBpbmcgPSBhZXMoeD0gZXN0aW1hdGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGxldmVsLCANCiAgICAgICAgICAgICAgICAgICAgICAgICB4bWluID0gY29uZi5sb3csIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHhtYXggPSBjb25mLmhpZ2gpKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAidG9tYXRvIiwgc2l6ZSA9MSkgKw0KICAgIGNvb3JkX2ZsaXAoKSArDQogICAgbGFicyh4ID0gImxvZ2l0IiwNCiAgICAgICAgIHkgPSAiQXJlYXMiKSArDQogICAgZ2VvbV9wb2ludHJhbmdlKCkgKw0KICAgIHRoZW1lX2NsYXNzaWMoKSkNCg0KYGBgDQoNCiMjIyBRdWVzdGlvbjoNCg0KNC4xLiBBcmUgdGhlcmUgYW55IGFyZWFzIHRoYXQgYXJlIHNpZ25pZmljYW50bHkgYWJvdmUgb3IgYmVsb3cgYXZlcmFnZT8NCg0KKipBbnN3ZXI6KiogRXZlbiB0aG91Z2ggdGhlIHBsb3QgbWF5IGJlIGhhcmQgdG8gaW5zcGVjdCBjbG9zZWx5LCBpdCBzZWVtcyBhcyBpZiBhbGwgYXJlYXMgb3ZlcmxhcCB3aXRoIHplcm8gKG5hdGlvbmFsIGF2ZXJhZ2UpLiBUaGlzIHdvdWxkIG1lYW4gdGhhdCBoYXZpbmcgY29udHJvbGxlZCBmb3Igc2V4LCBubyBhcmVhcyBhcmUgc2lnbmlmaWNhbnRseSBiZWxvdyBvciBhYm92ZSBhdmVyYWdlLg0KDQoqKioNCg0KIyBBZGRpdGlvbmFsIHRhc2tzDQoNCllvdSBjYW4gYm9ycm93IGZyb20gcHJhY3RpY2FsIDIgYW5kIHJlcGVhdCBzb21lIG9mIHRoZSB0YXNrcyB0aGVyZSwgc3VjaCBhczoNCg0KLSBhZGRpbmcgbW9yZSBleHBsYW5hdG9yeSB2YXJpYWJsZXMgDQoNCi0gYWRkaW5nIGludGVyYWN0aW9uIGVmZmVjdHMNCg0KLSBwbG90IHByZWRpY3Rpb25zLg==