Looking at the cats data:

set.seed(4210)
cats |> 
  slice_sample(n = 10)
##    body heart
## 1   2.1   7.6
## 2   2.9  10.1
## 3   2.5  11.0
## 4   3.6  15.0
## 5   2.9  11.8
## 6   3.2  12.3
## 7   2.2   8.7
## 8   2.5   8.8
## 9   2.2  11.0
## 10  3.1  12.1

Fitting the linear regression model

We’ll start by fitting a linear model for heart weight by body weight using lm(hearty ~ body, cats)

cats_lm <- lm(heart ~ body, cats)

summary(cats_lm)
## 
## Call:
## lm(formula = heart ~ body, data = cats)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.5694 -0.9634 -0.0921  1.0426  5.1238 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -0.3567     0.6923  -0.515    0.607    
## body          4.0341     0.2503  16.119   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.452 on 142 degrees of freedom
## Multiple R-squared:  0.6466, Adjusted R-squared:  0.6441 
## F-statistic: 259.8 on 1 and 142 DF,  p-value: < 2.2e-16

Adding the predicted heart weight to the data

While there are multiple ways to adding \(\widehat{heart}\) to the cats data set, the code chunk below uses predict(model, data) to do it:

cats <- 
  cats |> 
  mutate(heart_hat = predict(cats_lm, newdata = cats))


cats |> 
  slice_sample(n = 10)
##    body heart heart_hat
## 1   3.0  13.0 11.745526
## 2   2.8  10.2 10.938713
## 3   2.3   9.6  8.921682
## 4   2.1   8.1  8.114869
## 5   2.9  10.1 11.342119
## 6   2.0   7.4  7.711463
## 7   2.2  11.0  8.518276
## 8   2.3   9.0  8.921682
## 9   3.4  14.4 13.359151
## 10  2.8  13.3 10.938713

ANOVA: Sums of Squares

To perform an ANOVA test, we need three sums of squares from the regression model:

  1. \(SSTO\): The total sums of squares - The difference between \(Y\) and the average, \(\bar{Y}\)

\[SSTO = \sum_i^n(Y_i - \bar{Y})^2\]

SSTO <- sum((cats$heart - mean(cats$heart))^2)

round(SSTO, 2)
## [1] 847.63
  1. \(SSR\): The regression sums of squares - The difference between \(\hat{Y}_i\) and \(\bar{Y}\)

\[SSR = \sum_i^n(\hat{Y}_i - \bar{Y})^2\]

SSR <- sum((cats$heart_hat - mean(cats$heart))^2)

round(SSR, 2)
## [1] 548.09
  1. \(SSE\): The error sums of squares - The difference between \(Y_i\) and \(\hat{Y}_i\)

\[SSE = \sum_i^n(\hat{Y}_i - \bar{Y})^2\]

SSR <- sum((cats$heart - cats$heart_hat)^2)

round(SSR, 2)
## [1] 299.53

I’ll put them all together into one data frame using summarize(...)

SS_table <- 
  cats |> 
  summarize(
    Reg   = sum((heart_hat - mean(heart))^2),
    Error = sum((heart - heart_hat)^2),
    Total = sum((heart - mean(heart))^2)
  ) |> 
  # Pivoting the data frame so it looks like an ANOVA table
  pivot_longer(
    cols = everything(),
    names_to = 'source',
    values_to = 'SS'
  )

SS_table
## # A tibble: 3 × 2
##   source    SS
##   <chr>  <dbl>
## 1 Reg     548.
## 2 Error   300.
## 3 Total   848.

Full ANOVA table

Let’s add the degrees of freedom and MS columns to SS_table to make it into an ANOVA table:

ANOVA_table <- 
  SS_table |> 
  mutate(   # SSR df        # SSE df       # SSTO df
    df = c(        1, nrow(cats) - 2, nrow(cats) - 1),
    MS = SS / df
  )

ANOVA_table
## # A tibble: 3 × 4
##   source    SS    df     MS
##   <chr>  <dbl> <dbl>  <dbl>
## 1 Reg     548.     1 548.  
## 2 Error   300.   142   2.11
## 3 Total   848.   143   5.93

Note: The MS value in the Error column is our MSE we found the last few R examples!

Additional Note: While our hand made ANOVA table has \(MST\), we don’t typically calculate it since we never use it.

Conducting the ANOVA test

Once we have the complete ANOVA table, we can perform our ANOVA test.

The null and alternative hypotheses are:

\[H_0: \text{None of the predictors are linearly related to the response}\]

\[H_a: \text{At least one of the predictors is linearly related to the response}\]

or with context to our problem

\[H_0: \text{Body weight is linearly not related to heart weight in cats} \\ H_a: \text{Body weight is linearly related to heart weight in cats}\]

The test statistic is the ratio of the Mean Squared Regression vs Mean Squared Error:

\[F = MSR/MSE \sim F(1, n-2)\]

F_stat <- ANOVA_table$MS[1] / ANOVA_table$MS[2]

c(
  'F stat' = F_stat,
  'num df' = ANOVA_table$df[1],
  'den df' = ANOVA_table$df[2]
) |> 
  round(2)
## F stat num df den df 
## 259.83   1.00 142.00

From there, we can find our p-value using the stated \(F\)-distribution above:

\[P(F(1, n - 2) > F)\]

pf(F_stat, df1 = ANOVA_table$df[1], df2 = ANOVA_table$df[2], lower.tail = F)
## [1] 6.969045e-34

We have very, very strong evidence that body weight is linearly related to heart weight!

Performing the ANOVA test using built in functions

Since ANOVA tests with regression are very, very common, there are plenty of functions to do what we just did above without us having to manually calculate every \(SS\), df, and \(MS\) value.

Method 1: summary(model)

The summary function that we saw initially will perform our test for us:

summary(cats_lm)
## 
## Call:
## lm(formula = heart ~ body, data = cats)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.5694 -0.9634 -0.0921  1.0426  5.1238 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -0.3567     0.6923  -0.515    0.607    
## body          4.0341     0.2503  16.119   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.452 on 142 degrees of freedom
## Multiple R-squared:  0.6466, Adjusted R-squared:  0.6441 
## F-statistic: 259.8 on 1 and 142 DF,  p-value: < 2.2e-16

Where is it located in all that output?

If you look at the last line, you’ll see the test statistic, degrees of freedom, and p-value. All of them agree with what we found!

Method 2: glance(model) in broom package

In a previous example, we used the tidy() function from the broom package to find the p-value for the test of the slope alone.

The same package has a function called glance() that will return several fit statistics for the entire model in a one-row data frame:

broom::glance(cats_lm)
## # A tibble: 1 × 12
##   r.squared adj.r.squared sigma statistic  p.value    df logLik   AIC   BIC
##       <dbl>         <dbl> <dbl>     <dbl>    <dbl> <dbl>  <dbl> <dbl> <dbl>
## 1     0.647         0.644  1.45      260. 6.97e-34     1  -257.  520.  529.
## # ℹ 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>

That’s a lot of fit statistics! Let’s just look at the relevant ones:

broom::glance(cats_lm) |> 
  dplyr::select(statistic, df, df.residual, p.value)
## # A tibble: 1 × 4
##   statistic    df df.residual  p.value
##       <dbl> <dbl>       <int>    <dbl>
## 1      260.     1         142 6.97e-34

The above also agrees with what we found!

Method 3: anova(model)

The anova() function will perform an ANOVA test, but instead of testing the entire model, it will be a test for each predictor in the model. Which is more similar to the \(t\)-test that we did earlier, at least for now…

anova(cats_lm)
## Analysis of Variance Table
## 
## Response: heart
##            Df Sum Sq Mean Sq F value    Pr(>F)    
## body        1 548.09  548.09  259.83 < 2.2e-16 ***
## Residuals 142 299.53    2.11                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

So what is the advantage of using anova() over summary() or tidy(), since both are testing each explanatory variable individually?

Once we start adding categorical explanatory variables, anova() will be important, but for now, it’s not beneficial.

SLR: F-test vs t-test

We get the same result conducting an F-test or a t-test. That’s not by coincidence!

The main four distributions (\(z\), \(t\), \(\chi^2\), \(F\)) are all connected!

It’s relevant here because the slope test is:

\[t = \frac{b_1}{\sqrt{MSE/S_{XX}}} \sim t(n-2)\]

If you take a \(t\)-distributed random variable and square it, you get an \(F\)-statistic with 1 numerator df and the denominator df are the same as the \(t\)-distributions!

\[t^2 \sim F(1; n-2)\]

If we take the test stat for the slope and square it, we should get the same as the \(F\) test stat:

broom::tidy(cats_lm) |> 
  slice(2) |> 
  dplyr::select(t_stat = statistic) |> 
  mutate(t_stat = t_stat^2)
## # A tibble: 1 × 1
##   t_stat
##    <dbl>
## 1   260.

which is the same as the \(F\) stat we’ve seen many times in this example!

Note: The F-test version is only equivalent to a two-tailed test. While we can do a left or right-tail test using the \(t\)-distribution, we can’t for the \(F\) version :(

LS0tDQp0aXRsZTogJ1NpbXBsZSBMaW5lYXIgUmVncmVzc2lvbjogQU5PVkEgLSBDYXRzJw0KYXV0aG9yOiAiQ2hhcHRlciAyIg0KZGF0ZTogIlNUQSA0MjEwIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGZpZ193aWR0aDogNg0KICAgIGZpZ19oZWlnaHQ6IDYNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHNtb290aF9zY3JvbGw6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KIyB0aWR5dmVyc2UgcGFja2FnZQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCiMgQ2F0cyBkYXRhDQpjYXRzIDwtIA0KICBNQVNTOjpjYXRzIHw+IA0KICBkcGx5cjo6c2VsZWN0KA0KICAgIGJvZHkgPSBCd3QsDQogICAgaGVhcnQgPSBId3QNCiAgKQ0KYGBgDQoNCkxvb2tpbmcgYXQgdGhlIGNhdHMgZGF0YToNCg0KYGBge3IgY2F0c19kYXRhfQ0Kc2V0LnNlZWQoNDIxMCkNCmNhdHMgfD4gDQogIHNsaWNlX3NhbXBsZShuID0gMTApDQpgYGANCg0KIyMgRml0dGluZyB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwNCg0KV2UnbGwgc3RhcnQgYnkgZml0dGluZyBhIGxpbmVhciBtb2RlbCBmb3IgaGVhcnQgd2VpZ2h0IGJ5IGJvZHkgd2VpZ2h0IHVzaW5nIGBsbShoZWFydHkgfiBib2R5LCBjYXRzKWANCg0KYGBge3IgZml0X2xtfQ0KY2F0c19sbSA8LSBsbShoZWFydCB+IGJvZHksIGNhdHMpDQoNCnN1bW1hcnkoY2F0c19sbSkNCmBgYA0KDQojIyMgQWRkaW5nIHRoZSBwcmVkaWN0ZWQgaGVhcnQgd2VpZ2h0IHRvIHRoZSBkYXRhDQoNCldoaWxlIHRoZXJlIGFyZSBtdWx0aXBsZSB3YXlzIHRvIGFkZGluZyAkXHdpZGVoYXR7aGVhcnR9JCB0byB0aGUgKipjYXRzKiogZGF0YSBzZXQsIHRoZSBjb2RlIGNodW5rIGJlbG93IHVzZXMgYHByZWRpY3QobW9kZWwsIGRhdGEpYCB0byBkbyBpdDoNCg0KYGBge3IgYWRkX2hlYXJ0X2hhdH0NCmNhdHMgPC0gDQogIGNhdHMgfD4gDQogIG11dGF0ZShoZWFydF9oYXQgPSBwcmVkaWN0KGNhdHNfbG0sIG5ld2RhdGEgPSBjYXRzKSkNCg0KDQpjYXRzIHw+IA0KICBzbGljZV9zYW1wbGUobiA9IDEwKQ0KYGBgDQoNCg0KIyMgQU5PVkE6IFN1bXMgb2YgU3F1YXJlcw0KDQpUbyBwZXJmb3JtIGFuIEFOT1ZBIHRlc3QsIHdlIG5lZWQgdGhyZWUgc3VtcyBvZiBzcXVhcmVzIGZyb20gdGhlIHJlZ3Jlc3Npb24gbW9kZWw6DQoNCjEpICRTU1RPJDogVGhlIHRvdGFsIHN1bXMgb2Ygc3F1YXJlcyAtIFRoZSBkaWZmZXJlbmNlIGJldHdlZW4gJFkkIGFuZCB0aGUgYXZlcmFnZSwgJFxiYXJ7WX0kDQoNCiQkU1NUTyA9IFxzdW1faV5uKFlfaSAtIFxiYXJ7WX0pXjIkJA0KDQpgYGB7ciBTU1RPfQ0KU1NUTyA8LSBzdW0oKGNhdHMkaGVhcnQgLSBtZWFuKGNhdHMkaGVhcnQpKV4yKQ0KDQpyb3VuZChTU1RPLCAyKQ0KYGBgDQoNCg0KMikgJFNTUiQ6IFRoZSByZWdyZXNzaW9uIHN1bXMgb2Ygc3F1YXJlcyAtIFRoZSBkaWZmZXJlbmNlIGJldHdlZW4gJFxoYXR7WX1faSQgYW5kICRcYmFye1l9JA0KDQokJFNTUiA9IFxzdW1faV5uKFxoYXR7WX1faSAtIFxiYXJ7WX0pXjIkJA0KDQpgYGB7ciBTU1J9DQpTU1IgPC0gc3VtKChjYXRzJGhlYXJ0X2hhdCAtIG1lYW4oY2F0cyRoZWFydCkpXjIpDQoNCnJvdW5kKFNTUiwgMikNCmBgYA0KDQoNCjMpICRTU0UkOiBUaGUgZXJyb3Igc3VtcyBvZiBzcXVhcmVzIC0gVGhlIGRpZmZlcmVuY2UgYmV0d2VlbiAkWV9pJCBhbmQgJFxoYXR7WX1faSQNCiAgLSBBS0EsIHRoZSBzdW0gb2YgdGhlIHJlc2lkdWFscyBzcXVhcmVkLCB3aGljaCBpcyB0aGUgcXVhbnRpdHkgd2UgbWluaW1pemVkIHRvIGVzdGltYXRlICRiXzAkIGFuZCAkYl8xJA0KDQokJFNTRSA9IFxzdW1faV5uKFxoYXR7WX1faSAtIFxiYXJ7WX0pXjIkJA0KDQpgYGB7ciBTU0V9DQpTU1IgPC0gc3VtKChjYXRzJGhlYXJ0IC0gY2F0cyRoZWFydF9oYXQpXjIpDQoNCnJvdW5kKFNTUiwgMikNCmBgYA0KDQpJJ2xsIHB1dCB0aGVtIGFsbCB0b2dldGhlciBpbnRvIG9uZSBkYXRhIGZyYW1lIHVzaW5nIGBzdW1tYXJpemUoLi4uKWANCg0KYGBge3IgU1NfdGFibGV9DQpTU190YWJsZSA8LSANCiAgY2F0cyB8PiANCiAgc3VtbWFyaXplKA0KICAgIFJlZyAgID0gc3VtKChoZWFydF9oYXQgLSBtZWFuKGhlYXJ0KSleMiksDQogICAgRXJyb3IgPSBzdW0oKGhlYXJ0IC0gaGVhcnRfaGF0KV4yKSwNCiAgICBUb3RhbCA9IHN1bSgoaGVhcnQgLSBtZWFuKGhlYXJ0KSleMikNCiAgKSB8PiANCiAgIyBQaXZvdGluZyB0aGUgZGF0YSBmcmFtZSBzbyBpdCBsb29rcyBsaWtlIGFuIEFOT1ZBIHRhYmxlDQogIHBpdm90X2xvbmdlcigNCiAgICBjb2xzID0gZXZlcnl0aGluZygpLA0KICAgIG5hbWVzX3RvID0gJ3NvdXJjZScsDQogICAgdmFsdWVzX3RvID0gJ1NTJw0KICApDQoNClNTX3RhYmxlDQpgYGANCg0KIyMgRnVsbCBBTk9WQSB0YWJsZQ0KDQpMZXQncyBhZGQgdGhlIGRlZ3JlZXMgb2YgZnJlZWRvbSBhbmQgTVMgY29sdW1ucyB0byAqKlNTX3RhYmxlKiogdG8gbWFrZSBpdCBpbnRvIGFuIEFOT1ZBIHRhYmxlOg0KDQpgYGB7ciBBTk9WQV90YWJsZX0NCkFOT1ZBX3RhYmxlIDwtIA0KICBTU190YWJsZSB8PiANCiAgbXV0YXRlKCAgICMgU1NSIGRmICAgICAgICAjIFNTRSBkZiAgICAgICAjIFNTVE8gZGYNCiAgICBkZiA9IGMoICAgICAgICAxLCBucm93KGNhdHMpIC0gMiwgbnJvdyhjYXRzKSAtIDEpLA0KICAgIE1TID0gU1MgLyBkZg0KICApDQoNCkFOT1ZBX3RhYmxlDQpgYGANCg0KKipOb3RlOioqIFRoZSBNUyB2YWx1ZSBpbiB0aGUgKkVycm9yKiBjb2x1bW4gaXMgb3VyIE1TRSB3ZSBmb3VuZCB0aGUgbGFzdCBmZXcgUiBleGFtcGxlcyENCg0KKipBZGRpdGlvbmFsIE5vdGU6KiogV2hpbGUgb3VyIGhhbmQgbWFkZSBBTk9WQSB0YWJsZSBoYXMgJE1TVCQsIHdlIGRvbid0IHR5cGljYWxseSBjYWxjdWxhdGUgaXQgc2luY2Ugd2UgbmV2ZXIgdXNlIGl0Lg0KDQojIyBDb25kdWN0aW5nIHRoZSBBTk9WQSB0ZXN0DQoNCk9uY2Ugd2UgaGF2ZSB0aGUgY29tcGxldGUgQU5PVkEgdGFibGUsIHdlIGNhbiBwZXJmb3JtIG91ciBBTk9WQSB0ZXN0Lg0KDQpUaGUgbnVsbCBhbmQgYWx0ZXJuYXRpdmUgaHlwb3RoZXNlcyBhcmU6DQoNCiQkSF8wOiBcdGV4dHtOb25lIG9mIHRoZSBwcmVkaWN0b3JzIGFyZSBsaW5lYXJseSByZWxhdGVkIHRvIHRoZSByZXNwb25zZX0kJA0KDQokJEhfYTogXHRleHR7QXQgbGVhc3Qgb25lIG9mIHRoZSBwcmVkaWN0b3JzIGlzIGxpbmVhcmx5IHJlbGF0ZWQgdG8gdGhlIHJlc3BvbnNlfSQkDQoNCm9yIHdpdGggY29udGV4dCB0byBvdXIgcHJvYmxlbQ0KDQokJEhfMDogXHRleHR7Qm9keSB3ZWlnaHQgaXMgbGluZWFybHkgbm90IHJlbGF0ZWQgdG8gaGVhcnQgd2VpZ2h0IGluIGNhdHN9IFxcIEhfYTogXHRleHR7Qm9keSB3ZWlnaHQgaXMgbGluZWFybHkgcmVsYXRlZCB0byBoZWFydCB3ZWlnaHQgaW4gY2F0c30kJA0KDQpUaGUgdGVzdCBzdGF0aXN0aWMgaXMgdGhlIHJhdGlvIG9mIHRoZSBNZWFuIFNxdWFyZWQgUmVncmVzc2lvbiB2cyBNZWFuIFNxdWFyZWQgRXJyb3I6DQoNCiQkRiA9IE1TUi9NU0UgXHNpbSBGKDEsIG4tMikkJA0KDQpgYGB7ciBBTk9WQV90ZXN0fQ0KRl9zdGF0IDwtIEFOT1ZBX3RhYmxlJE1TWzFdIC8gQU5PVkFfdGFibGUkTVNbMl0NCg0KYygNCiAgJ0Ygc3RhdCcgPSBGX3N0YXQsDQogICdudW0gZGYnID0gQU5PVkFfdGFibGUkZGZbMV0sDQogICdkZW4gZGYnID0gQU5PVkFfdGFibGUkZGZbMl0NCikgfD4gDQogIHJvdW5kKDIpDQpgYGANCg0KRnJvbSB0aGVyZSwgd2UgY2FuIGZpbmQgb3VyIHAtdmFsdWUgdXNpbmcgdGhlIHN0YXRlZCAkRiQtZGlzdHJpYnV0aW9uIGFib3ZlOg0KDQokJFAoRigxLCBuIC0gMikgPiBGKSQkDQoNCmBgYHtyIHBfdmFsfQ0KcGYoRl9zdGF0LCBkZjEgPSBBTk9WQV90YWJsZSRkZlsxXSwgZGYyID0gQU5PVkFfdGFibGUkZGZbMl0sIGxvd2VyLnRhaWwgPSBGKQ0KYGBgDQoNCldlIGhhdmUgdmVyeSwgdmVyeSBzdHJvbmcgZXZpZGVuY2UgdGhhdCBib2R5IHdlaWdodCBpcyBsaW5lYXJseSByZWxhdGVkIHRvIGhlYXJ0IHdlaWdodCENCg0KIyMgUGVyZm9ybWluZyB0aGUgQU5PVkEgdGVzdCB1c2luZyBidWlsdCBpbiBmdW5jdGlvbnMNCg0KU2luY2UgQU5PVkEgdGVzdHMgd2l0aCByZWdyZXNzaW9uIGFyZSB2ZXJ5LCB2ZXJ5IGNvbW1vbiwgdGhlcmUgYXJlIHBsZW50eSBvZiBmdW5jdGlvbnMgdG8gZG8gd2hhdCB3ZSBqdXN0IGRpZCBhYm92ZSB3aXRob3V0IHVzIGhhdmluZyB0byBtYW51YWxseSBjYWxjdWxhdGUgZXZlcnkgJFNTJCwgZGYsIGFuZCAkTVMkIHZhbHVlLg0KDQojIyMgTWV0aG9kIDE6IGBzdW1tYXJ5KG1vZGVsKWANCg0KVGhlIHN1bW1hcnkgZnVuY3Rpb24gdGhhdCB3ZSBzYXcgaW5pdGlhbGx5IHdpbGwgcGVyZm9ybSBvdXIgdGVzdCBmb3IgdXM6DQoNCmBgYHtyIGxtX3N1bW1hcnl9DQpzdW1tYXJ5KGNhdHNfbG0pDQpgYGANCg0KV2hlcmUgaXMgaXQgbG9jYXRlZCBpbiBhbGwgdGhhdCBvdXRwdXQ/DQoNCklmIHlvdSBsb29rIGF0IHRoZSBsYXN0IGxpbmUsIHlvdSdsbCBzZWUgdGhlIHRlc3Qgc3RhdGlzdGljLCBkZWdyZWVzIG9mIGZyZWVkb20sIGFuZCBwLXZhbHVlLiBBbGwgb2YgdGhlbSBhZ3JlZSB3aXRoIHdoYXQgd2UgZm91bmQhDQoNCg0KIyMjIE1ldGhvZCAyOiBgZ2xhbmNlKG1vZGVsKWAgaW4gYGJyb29tYCBwYWNrYWdlDQoNCkluIGEgcHJldmlvdXMgZXhhbXBsZSwgd2UgdXNlZCB0aGUgYHRpZHkoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYGJyb29tYCBwYWNrYWdlIHRvIGZpbmQgdGhlIHAtdmFsdWUgZm9yIHRoZSB0ZXN0IG9mIHRoZSBzbG9wZSBhbG9uZS4NCg0KVGhlIHNhbWUgcGFja2FnZSBoYXMgYSBmdW5jdGlvbiBjYWxsZWQgYGdsYW5jZSgpYCB0aGF0IHdpbGwgcmV0dXJuIHNldmVyYWwgZml0IHN0YXRpc3RpY3MgZm9yIHRoZSBlbnRpcmUgbW9kZWwgaW4gYSBvbmUtcm93IGRhdGEgZnJhbWU6DQoNCg0KYGBge3IgZ2xhbmNlfQ0KYnJvb206OmdsYW5jZShjYXRzX2xtKQ0KYGBgDQoNClRoYXQncyBhIGxvdCBvZiBmaXQgc3RhdGlzdGljcyEgTGV0J3MganVzdCBsb29rIGF0IHRoZSByZWxldmFudCBvbmVzOg0KDQpgYGB7ciBnbGFuY2UyfQ0KYnJvb206OmdsYW5jZShjYXRzX2xtKSB8PiANCiAgZHBseXI6OnNlbGVjdChzdGF0aXN0aWMsIGRmLCBkZi5yZXNpZHVhbCwgcC52YWx1ZSkNCmBgYA0KDQpUaGUgYWJvdmUgYWxzbyBhZ3JlZXMgd2l0aCB3aGF0IHdlIGZvdW5kIQ0KDQojIyMgTWV0aG9kIDM6IGBhbm92YShtb2RlbClgDQoNClRoZSBgYW5vdmEoKWAgZnVuY3Rpb24gd2lsbCBwZXJmb3JtIGFuIEFOT1ZBIHRlc3QsIGJ1dCBpbnN0ZWFkIG9mIHRlc3RpbmcgdGhlIGVudGlyZSBtb2RlbCwgaXQgd2lsbCBiZSBhIHRlc3QgZm9yIGVhY2ggcHJlZGljdG9yIGluIHRoZSBtb2RlbC4gV2hpY2ggaXMgbW9yZSBzaW1pbGFyIHRvIHRoZSAkdCQtdGVzdCB0aGF0IHdlIGRpZCBlYXJsaWVyLCBhdCBsZWFzdCBmb3Igbm93Li4uDQoNCmBgYHtyIGFub3ZhX2xtfQ0KYW5vdmEoY2F0c19sbSkNCmBgYA0KDQpTbyB3aGF0IGlzIHRoZSBhZHZhbnRhZ2Ugb2YgdXNpbmcgYGFub3ZhKClgIG92ZXIgYHN1bW1hcnkoKWAgb3IgYHRpZHkoKWAsIHNpbmNlIGJvdGggYXJlIHRlc3RpbmcgZWFjaCBleHBsYW5hdG9yeSB2YXJpYWJsZSBpbmRpdmlkdWFsbHk/DQoNCk9uY2Ugd2Ugc3RhcnQgYWRkaW5nIGNhdGVnb3JpY2FsIGV4cGxhbmF0b3J5IHZhcmlhYmxlcywgYGFub3ZhKClgIHdpbGwgYmUgaW1wb3J0YW50LCBidXQgZm9yIG5vdywgaXQncyBub3QgYmVuZWZpY2lhbC4NCg0KDQojIyBTTFI6IEYtdGVzdCB2cyB0LXRlc3QNCg0KV2UgZ2V0IHRoZSBzYW1lIHJlc3VsdCBjb25kdWN0aW5nIGFuIEYtdGVzdCBvciBhIHQtdGVzdC4gVGhhdCdzIG5vdCBieSBjb2luY2lkZW5jZSENCg0KVGhlIG1haW4gZm91ciBkaXN0cmlidXRpb25zICgkeiQsICR0JCwgJFxjaGleMiQsICRGJCkgYXJlIGFsbCBjb25uZWN0ZWQhDQoNCiFbXShodHRwczovL20ubWVkaWEtYW1hem9uLmNvbS9pbWFnZXMvTS9NVjVCTnpJeFptSXpZakV0WkdNeVppMDBOREF3TFdKbU9Ea3RZVEF3T1dVMlpqa3daamRsWGtFeVhrRnFjR2NALl9WMV8uanBnKQ0KDQpJdCdzIHJlbGV2YW50IGhlcmUgYmVjYXVzZSB0aGUgc2xvcGUgdGVzdCBpczoNCg0KJCR0ID0gXGZyYWN7Yl8xfXtcc3FydHtNU0UvU197WFh9fX0gXHNpbSB0KG4tMikkJA0KDQpJZiB5b3UgdGFrZSBhICR0JC1kaXN0cmlidXRlZCByYW5kb20gdmFyaWFibGUgYW5kIHNxdWFyZSBpdCwgeW91IGdldCBhbiAkRiQtc3RhdGlzdGljIHdpdGggMSBudW1lcmF0b3IgZGYgYW5kIHRoZSBkZW5vbWluYXRvciBkZiBhcmUgdGhlIHNhbWUgYXMgdGhlICR0JC1kaXN0cmlidXRpb25zIQ0KDQokJHReMiBcc2ltIEYoMTsgbi0yKSQkDQoNCklmIHdlIHRha2UgdGhlIHRlc3Qgc3RhdCBmb3IgdGhlIHNsb3BlIGFuZCBzcXVhcmUgaXQsIHdlIHNob3VsZCBnZXQgdGhlIHNhbWUgYXMgdGhlICRGJCB0ZXN0IHN0YXQ6DQoNCmBgYHtyIHRfdnNfRn0NCmJyb29tOjp0aWR5KGNhdHNfbG0pIHw+IA0KICBzbGljZSgyKSB8PiANCiAgZHBseXI6OnNlbGVjdCh0X3N0YXQgPSBzdGF0aXN0aWMpIHw+IA0KICBtdXRhdGUodF9zdGF0ID0gdF9zdGF0XjIpDQpgYGANCg0Kd2hpY2ggaXMgdGhlIHNhbWUgYXMgdGhlICRGJCBzdGF0IHdlJ3ZlIHNlZW4gbWFueSB0aW1lcyBpbiB0aGlzIGV4YW1wbGUhDQoNCioqTm90ZToqKiBUaGUgRi10ZXN0IHZlcnNpb24gaXMgb25seSBlcXVpdmFsZW50IHRvIGEgKnR3by10YWlsZWQqIHRlc3QuIFdoaWxlIHdlIGNhbiBkbyBhIGxlZnQgb3IgcmlnaHQtdGFpbCB0ZXN0IHVzaW5nIHRoZSAkdCQtZGlzdHJpYnV0aW9uLCB3ZSBjYW4ndCBmb3IgdGhlICRGJCB2ZXJzaW9uIDooDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=