View the
latest recording of this lecture
We have mostly focused on (i) models containing just numerical
covariates (linear regression models) and (ii) models containing just
factors (‘ANOVA type’ models).
Naturally there will be situations in which we wish to model a
response in terms of both numerical covariates and
factors. We saw this in the case of the English Exam data.
Generalizing factorial and regression models to include models with
potentially both factors and numerical covariates produces the class of
models (historically) referred to as General
Linear Models.
The abbreviation GLM was used for these models for many years, and
still is in some software. R uses this acronym to refer to
generalised linear models which are covered in another
course. As a consequence, we often find some confusion, but all linear
models fit into the wider group of generalised linear models
We will look at general linear models in this lecture.
General Linear Models as Regressions
As we saw when we looked at factorial models, any factor can be coded
using dummy variables so as to produce a linear regression model.
It follows that any general linear model can be fitted as a linear
regression.
All the ideas about least squares estimation, fitted values and
residuals remain unchanged.
Models with a Single Factor and Covariate
In order to understand how to use and interpret models with a
combination of factors and numerical covariates, consider the simple
case where we have a response variable, y, a covariate
x and a factor A.
There are a number of different models that we could fit.
- differently sloped regressions for each level of A,
- parallel regressions at different levels of A,
- single regression model,
- model with zero slope, but distinct intercept for each Level of
A, and
- a null model having zero slope and common intercept.
image
Separate Regressions at Different Levels of A
\[Y_{ij} = \mu + \alpha_i + \beta_i x_{ij}
+ \varepsilon_{ij}\]
Note: intercept and slope depends on factor level i.
Treatment constraint fixes \(\alpha_1 =
0\).
R formula is Y ~ A + A:x
Here the notation of an interaction between the covariate and the
factor is telling R to allow the regression slope to depend on the
factor level.
Parallel Regressions at Different Levels of A
\[Y_{ij} = \mu + \alpha_i + \beta x_{ij} +
\varepsilon_{ij}\]
Note: constant slope, but intercept depends on factor level
i.
Treatment constraint sets \(\alpha_1 =
0\).
R formula is Y ~ A + x
Single Regression Model
\[Y_{ij} = \mu + \beta x_{ij} +
\varepsilon_{ij}\]
Note: factor A plays no role here.
R formula is the familiar Y ~ x
Technical aside:
Another possible model would be different regression slopes but a
common intercept, but this is not commonly used.
Linear Models for Samara Data
Samara are small winged fruit on maple trees. In Autumn these fruit
fall to the ground, spinning as they go. Research on the aerodynamics of
the fruit has applications for helicopter design.
samara image
In one study the following variables were measured on individual
samara:
Velocity
: speed of fall
Tree
: data collected from 3 trees
Load
: ‘disk loading’ (an aerodynamical quantity based
on each fruit’s size and weight).
The aim of this Case Study is to model Velocity
in terms
of Load
(a numerical covariate) and Tree
(a
factor).
Samara Data: R Code
Download samara.csv
## Samara <- read.csv(file = "samara.csv", header = TRUE)
str(Samara)
'data.frame': 35 obs. of 3 variables:
$ Tree : int 1 1 1 1 1 1 1 1 1 1 ...
$ Load : num 0.239 0.208 0.223 0.224 0.246 0.213 0.198 0.219 0.241 0.21 ...
$ Velocity: num 1.34 1.06 1.14 1.13 1.35 1.23 1.23 1.15 1.25 1.24 ...
Samara |>
mutate(TreeF = factor(Tree)) -> Samara
N.B. It might prove useful to have the values of Tree
as
both a numeric and a factor variable.
Samara Data: Plot of Data
Samara |>
ggplot(mapping = aes(y = Velocity, x = Load)) + geom_point(mapping = aes(col = TreeF,
pch = TreeF))
Model Fitting
We will fit a model with separate regression lines at different
levels of TreeF. The following shows the effect parameterising the model
two ways:
The first way gives estimates for the intercept and slope for each
line separately.
Samara.lm <- lm(Velocity ~ TreeF + TreeF:Load, data = Samara)
summary(Samara.lm)
Call:
lm(formula = Velocity ~ TreeF + TreeF:Load, data = Samara)
Residuals:
Min 1Q Median 3Q Max
-0.120023 -0.049465 -0.001298 0.049938 0.145571
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.5414 0.2632 2.057 0.0488 *
TreeF2 -0.8408 0.3356 -2.505 0.0181 *
TreeF3 -0.2987 0.4454 -0.671 0.5078
TreeF1:Load 3.0629 1.1599 2.641 0.0132 *
TreeF2:Load 6.7971 0.9511 7.147 7.26e-08 ***
TreeF3:Load 3.8834 1.9672 1.974 0.0580 .
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.07554 on 29 degrees of freedom
Multiple R-squared: 0.8436, Adjusted R-squared: 0.8167
F-statistic: 31.29 on 5 and 29 DF, p-value: 7.656e-11
Note the first model has TreeF1:Load, and the coefficients for
TreeF2:Load and TreeF3.Load are tested against the hypotheses that the
slope is zero for each of those trees. This may not be a sensible
hypothesis.
The second way specifies a baseline (for TreeF1), and then
adjustments to the intercept and slope for the second and third line.
The second way gives us insight into where there are significant
differences to the baseline, (if any).
Samara.lm2 <- lm(Velocity ~ TreeF * Load, data = Samara)
summary(Samara.lm2)
Call:
lm(formula = Velocity ~ TreeF * Load, data = Samara)
Residuals:
Min 1Q Median 3Q Max
-0.120023 -0.049465 -0.001298 0.049938 0.145571
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.5414 0.2632 2.057 0.0488 *
TreeF2 -0.8408 0.3356 -2.505 0.0181 *
TreeF3 -0.2987 0.4454 -0.671 0.5078
Load 3.0629 1.1599 2.641 0.0132 *
TreeF2:Load 3.7343 1.5000 2.490 0.0188 *
TreeF3:Load 0.8205 2.2837 0.359 0.7220
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.07554 on 29 degrees of freedom
Multiple R-squared: 0.8436, Adjusted R-squared: 0.8167
F-statistic: 31.29 on 5 and 29 DF, p-value: 7.656e-11
As usual, the treatment constraint is used by default for
TreeF
(the factor version of Tree
). Hence the
Intercept
listed under the coefficients is the intercept
for TreeF1
, with adjustments for treeF2 and 3 given in the
summary table. The relevant hypothesis tests are for whether the
intercepts and slopes are the same or different to that of the
baseline.
Either way we get the fitted model is as follows:
\[\begin{aligned}
&\mbox{Tree 1}&~~~E[ \mbox{Velocity}] = 0.5414 +
3.0629 \mbox{Load}\\
&\mbox{Tree 2}&~~~E[ \mbox{Velocity}] = -0.2994 +
6.7971 \mbox{Load}\\
&\mbox{Tree 3}&~~~E[ \mbox{Velocity}] = 0.2427 +
3.8834 \mbox{Load}\end{aligned}\]
Check that you can get the same numbers with either
parameterisation.
Design Matrix for the Samara Data
Write down the design matrix for the model
samara.lm
.
Verify your answer using the model.matrix()
command.
Samara.lm |>
model.matrix() |>
kable()
1 |
0 |
0 |
0.239 |
0.000 |
0.000 |
1 |
0 |
0 |
0.208 |
0.000 |
0.000 |
1 |
0 |
0 |
0.223 |
0.000 |
0.000 |
1 |
0 |
0 |
0.224 |
0.000 |
0.000 |
1 |
0 |
0 |
0.246 |
0.000 |
0.000 |
1 |
0 |
0 |
0.213 |
0.000 |
0.000 |
1 |
0 |
0 |
0.198 |
0.000 |
0.000 |
1 |
0 |
0 |
0.219 |
0.000 |
0.000 |
1 |
0 |
0 |
0.241 |
0.000 |
0.000 |
1 |
0 |
0 |
0.210 |
0.000 |
0.000 |
1 |
0 |
0 |
0.224 |
0.000 |
0.000 |
1 |
0 |
0 |
0.269 |
0.000 |
0.000 |
1 |
1 |
0 |
0.000 |
0.238 |
0.000 |
1 |
1 |
0 |
0.000 |
0.206 |
0.000 |
1 |
1 |
0 |
0.000 |
0.172 |
0.000 |
1 |
1 |
0 |
0.000 |
0.235 |
0.000 |
1 |
1 |
0 |
0.000 |
0.247 |
0.000 |
1 |
1 |
0 |
0.000 |
0.239 |
0.000 |
1 |
1 |
0 |
0.000 |
0.233 |
0.000 |
1 |
1 |
0 |
0.000 |
0.234 |
0.000 |
1 |
1 |
0 |
0.000 |
0.189 |
0.000 |
1 |
1 |
0 |
0.000 |
0.192 |
0.000 |
1 |
1 |
0 |
0.000 |
0.209 |
0.000 |
1 |
0 |
1 |
0.000 |
0.000 |
0.192 |
1 |
0 |
1 |
0.000 |
0.000 |
0.200 |
1 |
0 |
1 |
0.000 |
0.000 |
0.175 |
1 |
0 |
1 |
0.000 |
0.000 |
0.187 |
1 |
0 |
1 |
0.000 |
0.000 |
0.181 |
1 |
0 |
1 |
0.000 |
0.000 |
0.195 |
1 |
0 |
1 |
0.000 |
0.000 |
0.155 |
1 |
0 |
1 |
0.000 |
0.000 |
0.179 |
1 |
0 |
1 |
0.000 |
0.000 |
0.184 |
1 |
0 |
1 |
0.000 |
0.000 |
0.177 |
1 |
0 |
1 |
0.000 |
0.000 |
0.177 |
1 |
0 |
1 |
0.000 |
0.000 |
0.186 |
Samara.lm2 |>
model.matrix() |>
kable()
1 |
0 |
0 |
0.239 |
0.000 |
0.000 |
1 |
0 |
0 |
0.208 |
0.000 |
0.000 |
1 |
0 |
0 |
0.223 |
0.000 |
0.000 |
1 |
0 |
0 |
0.224 |
0.000 |
0.000 |
1 |
0 |
0 |
0.246 |
0.000 |
0.000 |
1 |
0 |
0 |
0.213 |
0.000 |
0.000 |
1 |
0 |
0 |
0.198 |
0.000 |
0.000 |
1 |
0 |
0 |
0.219 |
0.000 |
0.000 |
1 |
0 |
0 |
0.241 |
0.000 |
0.000 |
1 |
0 |
0 |
0.210 |
0.000 |
0.000 |
1 |
0 |
0 |
0.224 |
0.000 |
0.000 |
1 |
0 |
0 |
0.269 |
0.000 |
0.000 |
1 |
1 |
0 |
0.238 |
0.238 |
0.000 |
1 |
1 |
0 |
0.206 |
0.206 |
0.000 |
1 |
1 |
0 |
0.172 |
0.172 |
0.000 |
1 |
1 |
0 |
0.235 |
0.235 |
0.000 |
1 |
1 |
0 |
0.247 |
0.247 |
0.000 |
1 |
1 |
0 |
0.239 |
0.239 |
0.000 |
1 |
1 |
0 |
0.233 |
0.233 |
0.000 |
1 |
1 |
0 |
0.234 |
0.234 |
0.000 |
1 |
1 |
0 |
0.189 |
0.189 |
0.000 |
1 |
1 |
0 |
0.192 |
0.192 |
0.000 |
1 |
1 |
0 |
0.209 |
0.209 |
0.000 |
1 |
0 |
1 |
0.192 |
0.000 |
0.192 |
1 |
0 |
1 |
0.200 |
0.000 |
0.200 |
1 |
0 |
1 |
0.175 |
0.000 |
0.175 |
1 |
0 |
1 |
0.187 |
0.000 |
0.187 |
1 |
0 |
1 |
0.181 |
0.000 |
0.181 |
1 |
0 |
1 |
0.195 |
0.000 |
0.195 |
1 |
0 |
1 |
0.155 |
0.000 |
0.155 |
1 |
0 |
1 |
0.179 |
0.000 |
0.179 |
1 |
0 |
1 |
0.184 |
0.000 |
0.184 |
1 |
0 |
1 |
0.177 |
0.000 |
0.177 |
1 |
0 |
1 |
0.177 |
0.000 |
0.177 |
1 |
0 |
1 |
0.186 |
0.000 |
0.186 |
Note the difference in the column for TreeF1:Load
Checking models
Two different parameterisations of the same model will have different
coefficients in the summary()
output. They will have to
have the same number of coefficients though.
If you need to convince yourself that two different parameterisations
lead to the same underlying model, look at the fitted values for each
parameterisation.
Same fitted values means same residuals etc.
LS0tDQp0aXRsZTogIkxlY3R1cmUgMjU6IFRoZSBHZW5lcmFsIExpbmVhciBNb2RlbCINCnN1YnRpdGxlOiAxNjEuMjUxIFJlZ3Jlc3Npb24gTW9kZWxsaW5nDQphdXRob3I6ICJQcmVzZW50ZWQgYnkgSm9uYXRoYW4gR29kZnJleSA8YS5qLmdvZGZyZXlAbWFzc2V5LmFjLm56PiIgIA0KZGF0ZTogIldlZWsgOSBvZiBTZW1lc3RlciAyLCBgciBsdWJyaWRhdGU6OnllYXIobHVicmlkYXRlOjpub3coKSlgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICB0aGVtZTogeWV0aQ0KICAgIGhpZ2hsaWdodDogdGFuZ28NCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IHlldGkNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogIGlvc2xpZGVzX3ByZXNlbnRhdGlvbjoNCiAgICB3aWRlc2NyZWVuOiB0cnVlDQogICAgc21hbGxlcjogdHJ1ZQ0KICB3b3JkX2RvY3VtZW50OiBkZWZhdWx0DQogIHNsaWR5X3ByZXNlbnRhdGlvbjogDQogICAgdGhlbWU6IHlldGkNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KLS0tDQoNCg0KDQoNCg0KW1ZpZXcgdGhlIGxhdGVzdCByZWNvcmRpbmcgb2YgdGhpcyBsZWN0dXJlXShodHRwczovL1ItUmVzb3VyY2VzLm1hc3NleS5hYy5uei92aWRlb3MvMjUxTDI1Lm1wNCkNCjwhLS0tIERhdGEgaXMgb24NCmh0dHBzOi8vci1yZXNvdXJjZXMubWFzc2V5LmFjLm56L2RhdGEvMTYxMjUxLw0KLS0tPg0KDQpgYGB7ciBzZXR1cCwgcHVybD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkoa25pdHIpDQpvcHRzX2NodW5rJHNldChkZXY9YygicG5nIiwgInBkZiIpKQ0Kb3B0c19jaHVuayRzZXQoZmlnLmhlaWdodD02LCBmaWcud2lkdGg9NywgZmlnLnBhdGg9IkZpZ3VyZXMvIiwgZmlnLmFsdD0idW5sYWJlbGxlZCIpDQpvcHRzX2NodW5rJHNldChjb21tZW50PSIiLCBmaWcuYWxpZ249ImNlbnRlciIsIHRpZHk9VFJVRSkNCm9wdGlvbnMoa25pdHIua2FibGUuTkEgPSAnJykNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShicm9vbSkNCmBgYA0KDQoNCjwhLS0tIERvIG5vdCBlZGl0IGFueXRoaW5nIGFib3ZlIHRoaXMgbGluZS4gLS0tPg0KDQpXZSBoYXZlIG1vc3RseSBmb2N1c2VkIG9uIChpKSBtb2RlbHMgY29udGFpbmluZyBqdXN0IG51bWVyaWNhbCBjb3ZhcmlhdGVzIChsaW5lYXIgcmVncmVzc2lvbiBtb2RlbHMpIGFuZCAoaWkpIG1vZGVscyBjb250YWluaW5nIGp1c3QgZmFjdG9ycyAo4oCYQU5PVkEgdHlwZeKAmSBtb2RlbHMpLg0KDQpOYXR1cmFsbHkgdGhlcmUgd2lsbCBiZSBzaXR1YXRpb25zIGluIHdoaWNoIHdlIHdpc2ggdG8gbW9kZWwgYSByZXNwb25zZSBpbiB0ZXJtcyBvZiAqKmJvdGgqKiBudW1lcmljYWwgY292YXJpYXRlcyBhbmQgZmFjdG9ycy4gV2Ugc2F3IHRoaXMgaW4gdGhlIGNhc2Ugb2YgdGhlIEVuZ2xpc2ggRXhhbSBkYXRhLg0KDQpHZW5lcmFsaXppbmcgZmFjdG9yaWFsIGFuZCByZWdyZXNzaW9uIG1vZGVscyB0byBpbmNsdWRlIG1vZGVscyB3aXRoIHBvdGVudGlhbGx5IGJvdGggZmFjdG9ycyBhbmQgbnVtZXJpY2FsIGNvdmFyaWF0ZXMgcHJvZHVjZXMgdGhlIGNsYXNzIG9mIG1vZGVscyAoaGlzdG9yaWNhbGx5KSByZWZlcnJlZCB0byBhcyAqKkcqKmVuZXJhbCAqKkwqKmluZWFyICoqTSoqb2RlbHMuDQoNClRoZSBhYmJyZXZpYXRpb24gR0xNIHdhcyB1c2VkIGZvciB0aGVzZSBtb2RlbHMgZm9yIG1hbnkgeWVhcnMsIGFuZCBzdGlsbCBpcyBpbiBzb21lIHNvZnR3YXJlLiBSIHVzZXMgdGhpcyBhY3JvbnltIHRvIHJlZmVyIHRvICoqZ2VuZXJhbGlzZWQgbGluZWFyIG1vZGVscyoqIHdoaWNoIGFyZSBjb3ZlcmVkIGluIGFub3RoZXIgY291cnNlLiBBcyBhIGNvbnNlcXVlbmNlLCB3ZSAgb2Z0ZW4gZmluZCBzb21lIGNvbmZ1c2lvbiwgYnV0IGFsbCBsaW5lYXIgbW9kZWxzIGZpdCBpbnRvIHRoZSB3aWRlciBncm91cCBvZiBnZW5lcmFsaXNlZCBsaW5lYXIgbW9kZWxzICANCg0KV2Ugd2lsbCBsb29rIGF0IGdlbmVyYWwgbGluZWFyIG1vZGVscyBpbiB0aGlzIGxlY3R1cmUuDQoNCiMjIEdlbmVyYWwgTGluZWFyIE1vZGVscyBhcyBSZWdyZXNzaW9ucw0KDQpBcyB3ZSBzYXcgd2hlbiB3ZSBsb29rZWQgYXQgZmFjdG9yaWFsIG1vZGVscywgYW55IGZhY3RvciBjYW4gYmUgY29kZWQgdXNpbmcgZHVtbXkgdmFyaWFibGVzIHNvIGFzIHRvIHByb2R1Y2UgYSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbC4NCg0KSXQgZm9sbG93cyB0aGF0IGFueSBnZW5lcmFsIGxpbmVhciBtb2RlbCBjYW4gYmUgZml0dGVkIGFzIGEgbGluZWFyIHJlZ3Jlc3Npb24uDQoNCkFsbCB0aGUgaWRlYXMgYWJvdXQgbGVhc3Qgc3F1YXJlcyBlc3RpbWF0aW9uLCBmaXR0ZWQgdmFsdWVzIGFuZCByZXNpZHVhbHMgcmVtYWluIHVuY2hhbmdlZC4NCg0KIyMgTW9kZWxzIHdpdGggYSBTaW5nbGUgRmFjdG9yIGFuZCBDb3ZhcmlhdGUNCg0KSW4gb3JkZXIgdG8gdW5kZXJzdGFuZCBob3cgdG8gdXNlIGFuZCBpbnRlcnByZXQgbW9kZWxzIHdpdGggYSAgY29tYmluYXRpb24gb2YgZmFjdG9ycyBhbmQgbnVtZXJpY2FsIGNvdmFyaWF0ZXMsIGNvbnNpZGVyIHRoZSBzaW1wbGUgIGNhc2Ugd2hlcmUgd2UgaGF2ZSBhIHJlc3BvbnNlIHZhcmlhYmxlLCAqeSosIGEgY292YXJpYXRlICp4KiBhbmQgIGEgZmFjdG9yICpBKi4NCg0KVGhlcmUgYXJlIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBtb2RlbHMgdGhhdCB3ZSBjb3VsZCBmaXQuDQogICAgDQotIGRpZmZlcmVudGx5IHNsb3BlZCByZWdyZXNzaW9ucyBmb3IgZWFjaCBsZXZlbCBvZiAqQSosDQotIHBhcmFsbGVsIHJlZ3Jlc3Npb25zIGF0IGRpZmZlcmVudCBsZXZlbHMgb2YgKkEqLA0KLSBzaW5nbGUgcmVncmVzc2lvbiBtb2RlbCwNCi0gbW9kZWwgd2l0aCB6ZXJvIHNsb3BlLCBidXQgZGlzdGluY3QgaW50ZXJjZXB0IGZvciBlYWNoIExldmVsIG9mICpBKiwgYW5kDQotIGEgbnVsbCBtb2RlbCBoYXZpbmcgemVybyBzbG9wZSBhbmQgY29tbW9uIGludGVyY2VwdC4NCg0KDQoNCiFbaW1hZ2VdKC4uL2dyYXBoaWNzL2dsbS5wbmcpDQoNCiMjIyBTZXBhcmF0ZSBSZWdyZXNzaW9ucyBhdCBEaWZmZXJlbnQgTGV2ZWxzIG9mICpBKg0KDQokJFlfe2lqfSA9IFxtdSArIFxhbHBoYV9pICsgXGJldGFfaSB4X3tpan0gKyBcdmFyZXBzaWxvbl97aWp9JCQNCg0KTm90ZTogaW50ZXJjZXB0IGFuZCBzbG9wZSBkZXBlbmRzIG9uIGZhY3RvciBsZXZlbCAqaSouICANClRyZWF0bWVudCBjb25zdHJhaW50IGZpeGVzICRcYWxwaGFfMSA9IDAkLg0KDQpSIGZvcm11bGEgaXMgYFkgfiBBICsgQTp4YA0KDQpIZXJlIHRoZSBub3RhdGlvbiBvZiBhbiBpbnRlcmFjdGlvbiBiZXR3ZWVuIHRoZSBjb3ZhcmlhdGUgYW5kIHRoZSBmYWN0b3INCmlzIHRlbGxpbmcgUiB0byBhbGxvdyB0aGUgcmVncmVzc2lvbiBzbG9wZSB0byBkZXBlbmQgb24gdGhlIGZhY3Rvcg0KbGV2ZWwuDQoNCiMjIyBQYXJhbGxlbCBSZWdyZXNzaW9ucyBhdCBEaWZmZXJlbnQgTGV2ZWxzIG9mICpBKg0KDQokJFlfe2lqfSA9IFxtdSArIFxhbHBoYV9pICsgXGJldGEgeF97aWp9ICsgXHZhcmVwc2lsb25fe2lqfSQkDQoNCk5vdGU6IGNvbnN0YW50IHNsb3BlLCBidXQgaW50ZXJjZXB0IGRlcGVuZHMgb24gZmFjdG9yIGxldmVsICppKi4gIA0KVHJlYXRtZW50IGNvbnN0cmFpbnQgc2V0cyAkXGFscGhhXzEgPSAwJC4NCg0KUiBmb3JtdWxhIGlzIGBZIH4gQSArIHhgDQoNCiMjIyBTaW5nbGUgUmVncmVzc2lvbiBNb2RlbA0KDQokJFlfe2lqfSA9IFxtdSArIFxiZXRhIHhfe2lqfSArIFx2YXJlcHNpbG9uX3tpan0kJA0KDQpOb3RlOiBmYWN0b3IgKkEqIHBsYXlzIG5vIHJvbGUgaGVyZS4NCg0KUiBmb3JtdWxhIGlzIHRoZSBmYW1pbGlhciBgWSB+IHhgDQoNCipUZWNobmljYWwgYXNpZGU6KiAgDQoNCkFub3RoZXIgcG9zc2libGUgbW9kZWwgd291bGQgYmUgZGlmZmVyZW50IHJlZ3Jlc3Npb24gc2xvcGVzIGJ1dCBhIGNvbW1vbg0KaW50ZXJjZXB0LCBidXQgdGhpcyBpcyBub3QgY29tbW9ubHkgdXNlZC4NCg0KIyMgSW50ZXJwcmV0YXRpb24gb2YgTGluZWFyIE1vZGVsIEZvcm11bGFlDQoNClRoZSBpZGVhcyBqdXN0IHByZXNlbnRlZCBjYW4gYmUgZXh0ZW5kZWQgaW4gYSBuYXR1cmFsIGZhc2hpb24gdG8NCm1vZGVscyB3aXRoIHR3byBvciBtb3JlIGZhY3RvcnMgYW5kIGNvdmFyaWF0ZXMuDQoNClN1cHBvc2UgdGhhdCB0aGUgUiBmb3JtdWxhIGlzIGBZIH4gQSArIEE6QiArIEE6eCArIHpgLCB3aGVyZSBgQWAgYW5kIGBCYCBhcmUgZmFjdG9ycywgYW5kIGB4YCBhbmQgYHpgIGFyZSBjb3ZhcmlhdGVzLiBUaGVuIHRoZSBtb2RlbCBpcyBkZWZpbmVkIGJ5Og0KDQokJFlfe2lqa30gPSBcbXUgK1xhbHBoYV9pICsgXGJldGFfe2lqfSArIFxnYW1tYV9pIHhfe2lqa30gKyANCiAgICAgXGRlbHRhIHpfe2lqa30gKyBcdmFyZXBzaWxvbl97aWprfS4kJA0KDQoNClN1cHBvc2UgdGhhdCB0aGUgUiBmb3JtdWxhIGlzIGBZIH4gQSpCICsgQTp4YA0KICAgIA0KVGhpcyBtb2RlbCBpcyBkZWZpbmVkIGJ5ICRZX3tpamt9ID0gXG11ICtcYWxwaGFfaSArIFxiZXRhX2ogKyAoXGFscGhhXGJldGEpX3tpan0gKyANCiAgICBcZ2FtbWFfaSB4X3tpamt9ICsgXHZhcmVwc2lsb25fe2lqa30uJA0KDQpTdXBwb3NlIHRoYXQgdGhlIFIgZm9ybXVsYSBpcyAgIGBZIH4gQSArIEE6eCArIEIgKyBCOnogYA0KICAgIA0KVGhpcyBtb2RlbCBpcyBkZWZpbmVkIGJ5ICRZX3tpamt9ID0gXG11ICtcYWxwaGFfaSArIFxiZXRhX2ogKyBcZ2FtbWFfe2l9IHhfe2lqa30gKyBcZGVsdGFfaiB6X3tpamt9ICsgXHZhcmVwc2lsb25fe2lqa30uJA0KDQojIyBMaW5lYXIgTW9kZWxzIGZvciBTYW1hcmEgRGF0YQ0KDQoNCg0KU2FtYXJhIGFyZSBzbWFsbCB3aW5nZWQgZnJ1aXQgb24gbWFwbGUgdHJlZXMuDQpJbiBBdXR1bW4gdGhlc2UgZnJ1aXQgZmFsbCB0byB0aGUgZ3JvdW5kLCBzcGlubmluZyBhcyB0aGV5IGdvLg0KUmVzZWFyY2ggb24gdGhlIGFlcm9keW5hbWljcyBvZiB0aGUgZnJ1aXQgaGFzIGFwcGxpY2F0aW9ucyBmb3IgaGVsaWNvcHRlciBkZXNpZ24uDQoNCiFbc2FtYXJhIGltYWdlXSguLi8uLi9ncmFwaGljcy9zYW1hcmEuanBnKQ0KDQoNCg0KSW4gb25lIHN0dWR5IHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzIHdlcmUgbWVhc3VyZWQgb24gaW5kaXZpZHVhbCBzYW1hcmE6DQoNCjEuIGBWZWxvY2l0eWA6IHNwZWVkIG9mIGZhbGwNCjIuIGBUcmVlYDogZGF0YSBjb2xsZWN0ZWQgZnJvbSAzIHRyZWVzDQozLiBgTG9hZGA6IOKAmGRpc2sgbG9hZGluZ+KAmSAoYW4gYWVyb2R5bmFtaWNhbCBxdWFudGl0eSBiYXNlZCBvbiBlYWNoDQogICAgICAgIGZydWl04oCZcyBzaXplIGFuZCB3ZWlnaHQpLg0KDQpUaGUgYWltIG9mIHRoaXMgQ2FzZSBTdHVkeSBpcyB0byBtb2RlbCBgVmVsb2NpdHlgIGluIHRlcm1zIG9mIGBMb2FkYCAoYSBudW1lcmljYWwgICAgY292YXJpYXRlKSBhbmQgYFRyZWVgIChhIGZhY3RvcikuDQoNCiMjIyBTYW1hcmEgRGF0YTogUiBDb2RlDQoNCg0KYHIgeGZ1bjo6ZW1iZWRfZmlsZSgiLi4vLi4vZGF0YS9zYW1hcmEuY3N2IilgDQoNCmBgYHtyIGdldFNhbWFyYSwgZWNobz0tMSwgZXZhbD0tMn0NClNhbWFyYSA8LSByZWFkLmNzdihmaWxlPSIuLi8uLi9kYXRhL3NhbWFyYS5jc3YiLCBoZWFkZXI9VFJVRSkNClNhbWFyYSA8LSByZWFkLmNzdihmaWxlPSJzYW1hcmEuY3N2IiwgaGVhZGVyPVRSVUUpDQpzdHIoU2FtYXJhKQ0KU2FtYXJhIHw+IG11dGF0ZShUcmVlRiA9IGZhY3RvcihUcmVlKSkgLT4gIFNhbWFyYQ0KYGBgDQoNCk4uQi4gSXQgbWlnaHQgcHJvdmUgdXNlZnVsIHRvIGhhdmUgdGhlIHZhbHVlcyBvZiBgVHJlZWAgYXMgYm90aCBhIG51bWVyaWMgYW5kIGEgZmFjdG9yIHZhcmlhYmxlLg0KDQojIyMgU2FtYXJhIERhdGE6IFBsb3Qgb2YgRGF0YQ0KDQpgYGB7ciBTYW1hcmFQbG90LCBmaWcuY2FwPSJTY2F0dGVyIHBsb3Qgb2YgZGF0YSB3aXRoIGRpZmZlcmVudCBjb2xvdXJzIGFuZCBwbG90dGluZyBzeW1ib2xzIHVzZWQgdG8gZGlzdGluZ3Vpc2ggZGF0YSBmcm9tIGRpZmZlcmVudCB0cmVlcy4ifQ0KU2FtYXJhIHw+DQpnZ3Bsb3QobWFwcGluZyA9IGFlcyh5PVZlbG9jaXR5LCB4PUxvYWQpKSArIA0KZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKGNvbD1UcmVlRiwgcGNoPVRyZWVGKSkNCmBgYA0KDQoNCg0KDQoNCiMjIyBNb2RlbCBGaXR0aW5nDQoNCg0KV2Ugd2lsbCBmaXQgYSBtb2RlbCB3aXRoIHNlcGFyYXRlIHJlZ3Jlc3Npb24gbGluZXMgYXQgZGlmZmVyZW50IGxldmVscyBvZiBUcmVlRi4gVGhlIGZvbGxvd2luZyBzaG93cyB0aGUgZWZmZWN0ICBwYXJhbWV0ZXJpc2luZyB0aGUgbW9kZWwgdHdvIHdheXM6DQoNClRoZSBmaXJzdCB3YXkgZ2l2ZXMgZXN0aW1hdGVzIGZvciB0aGUgaW50ZXJjZXB0IGFuZCBzbG9wZSBmb3IgZWFjaCBsaW5lIHNlcGFyYXRlbHkuIA0KIA0KYGBge3IgU2FtYXJhLmxtfQ0KU2FtYXJhLmxtIDwtIGxtKFZlbG9jaXR5IH4gVHJlZUYgKyBUcmVlRjpMb2FkLCBkYXRhPVNhbWFyYSkgDQpzdW1tYXJ5KFNhbWFyYS5sbSkNCmBgYA0KDQpOb3RlIHRoZSBmaXJzdCBtb2RlbCBoYXMgVHJlZUYxOkxvYWQsIGFuZCB0aGUgY29lZmZpY2llbnRzIGZvciBUcmVlRjI6TG9hZCBhbmQgVHJlZUYzLkxvYWQgYXJlIHRlc3RlZCAgYWdhaW5zdCB0aGUgaHlwb3RoZXNlcyB0aGF0IHRoZSBzbG9wZSBpcyB6ZXJvIGZvciBlYWNoIG9mIHRob3NlIHRyZWVzLiAgVGhpcyBtYXkgbm90IGJlIGEgc2Vuc2libGUgaHlwb3RoZXNpcy4NCg0KDQpUaGUgc2Vjb25kIHdheSBzcGVjaWZpZXMgYSBiYXNlbGluZSAoZm9yIFRyZWVGMSksICBhbmQgdGhlbiBhZGp1c3RtZW50cyB0byB0aGUgaW50ZXJjZXB0IGFuZCBzbG9wZSBmb3IgdGhlIHNlY29uZCBhbmQgdGhpcmQgbGluZS4gIFRoZSBzZWNvbmQgd2F5IGdpdmVzIHVzIGluc2lnaHQgaW50byB3aGVyZSB0aGVyZSBhcmUgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZXMgdG8gdGhlIGJhc2VsaW5lLCAoaWYgYW55KS4gDQogDQpgYGB7cn0NClNhbWFyYS5sbTIgPC0gbG0oVmVsb2NpdHkgfiBUcmVlRiAqIExvYWQsIGRhdGEgPSBTYW1hcmEpDQpzdW1tYXJ5KFNhbWFyYS5sbTIpDQpgYGANCg0KQXMgdXN1YWwsIHRoZSB0cmVhdG1lbnQgY29uc3RyYWludCBpcyB1c2VkIGJ5IGRlZmF1bHQgZm9yIGBUcmVlRmAgKHRoZSBmYWN0b3IgdmVyc2lvbiBvZiBgVHJlZWApLiBIZW5jZSB0aGUgYEludGVyY2VwdGAgbGlzdGVkIHVuZGVyIHRoZSBjb2VmZmljaWVudHMgaXMgdGhlIGludGVyY2VwdCBmb3IgYFRyZWVGMWAsICAgICAgd2l0aCBhZGp1c3RtZW50cyBmb3IgdHJlZUYyIGFuZCAzIGdpdmVuIGluIHRoZSBzdW1tYXJ5IHRhYmxlLiAgICAgVGhlIHJlbGV2YW50IGh5cG90aGVzaXMgdGVzdHMgYXJlIGZvciB3aGV0aGVyIHRoZSBpbnRlcmNlcHRzIGFuZCBzbG9wZXMgYXJlIHRoZSBzYW1lIG9yIGRpZmZlcmVudCB0byB0aGF0IG9mIHRoZSBiYXNlbGluZS4gDQoNCkVpdGhlciB3YXkgd2UgZ2V0IHRoZSBmaXR0ZWQgbW9kZWwgaXMgYXMgZm9sbG93czogDQoNCiQkXGJlZ2lue2FsaWduZWR9DQogICAgJlxtYm94e1RyZWUgMX0mfn5+RVsgXG1ib3h7VmVsb2NpdHl9XSA9IDAuNTQxNCArIDMuMDYyOSAgXG1ib3h7TG9hZH1cXA0KICAgICZcbWJveHtUcmVlIDJ9Jn5+fkVbIFxtYm94e1ZlbG9jaXR5fV0gPSAtMC4yOTk0ICsgNi43OTcxICBcbWJveHtMb2FkfVxcDQogICAgJlxtYm94e1RyZWUgM30mfn5+RVsgXG1ib3h7VmVsb2NpdHl9XSA9IDAuMjQyNyArIDMuODgzNCAgXG1ib3h7TG9hZH1cZW5ke2FsaWduZWR9JCQNCiAgICANCkNoZWNrIHRoYXQgeW91IGNhbiBnZXQgdGhlIHNhbWUgbnVtYmVycyB3aXRoIGVpdGhlciBwYXJhbWV0ZXJpc2F0aW9uLg0KDQoNCiMjIyBEZXNpZ24gTWF0cml4IGZvciB0aGUgU2FtYXJhIERhdGEgDQoNCldyaXRlIGRvd24gdGhlIGRlc2lnbiBtYXRyaXggZm9yIHRoZSBtb2RlbCBgc2FtYXJhLmxtYC4gDQoNClZlcmlmeSB5b3VyIGFuc3dlciB1c2luZyB0aGUgYG1vZGVsLm1hdHJpeCgpYCBjb21tYW5kLg0KDQpgYGB7ciBTYW1hcmEgbW9kZWwgbWF0cml4fQ0KU2FtYXJhLmxtIHw+IG1vZGVsLm1hdHJpeCgpICB8PiBrYWJsZSgpDQpTYW1hcmEubG0yIHw+IG1vZGVsLm1hdHJpeCgpIHw+IGthYmxlKCkNCg0KYGBgDQoNCk5vdGUgdGhlIGRpZmZlcmVuY2UgaW4gdGhlIGNvbHVtbiBmb3IgVHJlZUYxOkxvYWQNCg0KIyMgQ2hlY2tpbmcgbW9kZWxzDQoNClR3byBkaWZmZXJlbnQgcGFyYW1ldGVyaXNhdGlvbnMgb2YgdGhlIHNhbWUgbW9kZWwgd2lsbCBoYXZlIGRpZmZlcmVudCBjb2VmZmljaWVudHMgaW4gdGhlIGBzdW1tYXJ5KClgIG91dHB1dC4gVGhleSB3aWxsIGhhdmUgdG8gaGF2ZSB0aGUgc2FtZSBudW1iZXIgb2YgY29lZmZpY2llbnRzIHRob3VnaC4NCg0KDQpJZiB5b3UgbmVlZCB0byBjb252aW5jZSB5b3Vyc2VsZiB0aGF0IHR3byBkaWZmZXJlbnQgcGFyYW1ldGVyaXNhdGlvbnMgbGVhZCB0byB0aGUgc2FtZSB1bmRlcmx5aW5nIG1vZGVsLCBsb29rIGF0IHRoZSBmaXR0ZWQgdmFsdWVzIGZvciBlYWNoIHBhcmFtZXRlcmlzYXRpb24uDQoNClNhbWUgZml0dGVkIHZhbHVlcyBtZWFucyBzYW1lIHJlc2lkdWFscyBldGMuDQoNCg0K