Do blood counts or biochemistry show a relationship with PDFF?

In this notebook I will demonstrate several techniques for understanding the relation between a single continous outcome and a set of correlated variables. The dependent variable in this case study is Proton Density Fat Fraction (PDFF) which is defined as

“a measure of the proportion of a tissue which is composed of fat”, in this case derived from MRI imaging of the liver [from https://perspectum.com/media/1361/understanding-pdff.pdf].

Load the data and summarise the variables

source("R/0_setup.R")

library(caret)
library(pls)

IP<-   readRDS("DemoSet2.rds")

tests<- names(IP)[27:71]
sett<- IP %>% select(PDFF, all_of(tests)) %>% 
                filter(complete.cases(.)) %>%
                data.frame()
sk<- skim(sett) %>% select(variable= skim_variable, mean= numeric.mean, min= numeric.p0, median= numeric.p50,
                           max= numeric.p100, numeric.hist)
sk[, 2:4]<- apply(sk[, 2:4], 2, function(x) round(x, 2))
print(sk)

In the set with missing data removed, we have PDFF and biochemistry data for 2515 participants. Since PDFF is highly skewed and truncated at zero, a transformation is applied to make it easier for linear models to make sensible predictions of PDFF.

A Multiple Linear Model

The first method that comes to mind is a multiple linear regression model, where we might be tempted to use all our biochemstry data as X variables. However, as we will see below, this is bad idea where our X variables are correlated.

m1<- lm(PDFF~., data= sett) 
m1 %>% summary() %>% print()

Call:
lm(formula = PDFF ~ ., data = sett)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.6305 -0.1865 -0.0440  0.1339  1.6955 

Coefficients:
                                                Estimate Std. Error t value Pr(>|t|)    
(Intercept)                                    1.3091563  0.0062625 209.047  < 2e-16 ***
haematocrit_percentage                         0.2844307  0.1995569   1.425  0.15419    
red_blood_cell_erythrocyte_count               0.0571971  0.1602537   0.357  0.72119    
haemoglobin_concentration                     -0.3313760  0.1476659  -2.244  0.02491 *  
mean_corpuscular_volume                       -0.7168277  0.2527603  -2.836  0.00461 ** 
red_blood_cell_erythrocyte_distribution_width -0.0130327  0.0074308  -1.754  0.07957 .  
mean_corpuscular_haemoglobin                   0.8560445  0.2686840   3.186  0.00146 ** 
platelet_count                                 0.0267325  0.0481077   0.556  0.57848    
white_blood_cell_leukocyte_count              -0.5970714  0.2847785  -2.097  0.03613 *  
mean_corpuscular_haemoglobin_concentration    -0.3008519  0.1273568  -2.362  0.01824 *  
mean_platelet_thrombocyte_volume               0.0127022  0.0247567   0.513  0.60794    
platelet_crit                                 -0.0175185  0.0433749  -0.404  0.68633    
platelet_distribution_width                   -0.0087618  0.0073703  -1.189  0.23463    
basophill_percentage                           0.0474270  0.0409708   1.158  0.24715    
eosinophill_percentage                         0.2854742  0.2276292   1.254  0.20992    
lymphocyte_percentage                          1.6541060  1.1583432   1.428  0.15342    
monocyte_percentage                            0.5316702  0.3479571   1.528  0.12665    
neutrophill_percentage                         1.8229093  1.2894847   1.414  0.15758    
basophill_count                                0.0078867  0.0105680   0.746  0.45557    
eosinophill_count                              0.0699992  0.0276716   2.530  0.01148 *  
lymphocyte_count                               0.2450480  0.1164926   2.104  0.03552 *  
monocyte_count                                 0.0270995  0.0375491   0.722  0.47054    
neutrophill_count                              0.4936365  0.2307846   2.139  0.03254 *  
reticulocyte_count                            -0.0503671  0.1384470  -0.364  0.71604    
reticulocyte_percentage                        0.0288779  0.1344721   0.215  0.82998    
mean_reticulocyte_volume                       0.0107052  0.0103554   1.034  0.30134    
high_light_scatter_reticulocyte_percentage    -0.1906402  0.1236990  -1.541  0.12341    
mean_sphered_cell_volume                      -0.0153129  0.0111490  -1.373  0.16973    
high_light_scatter_reticulocyte_count          0.2961738  0.1238703   2.391  0.01688 *  
immature_reticulocyte_fraction                -0.0102351  0.0147230  -0.695  0.48701    
alkaline_phosphatase                          -0.0024304  0.0068407  -0.355  0.72241    
cholesterol                                   -0.0162209  0.0224310  -0.723  0.46966    
cystatin_c                                     0.0001916  0.0080584   0.024  0.98103    
alanine_aminotransferase                       0.0836863  0.0100436   8.332  < 2e-16 ***
creatinine                                    -0.0202820  0.0091358  -2.220  0.02651 *  
gamma_glutamyltransferase                      0.0005962  0.0080063   0.074  0.94064    
urea                                           0.0093931  0.0070524   1.332  0.18301    
triglycerides                                  0.0405936  0.0078859   5.148 2.85e-07 ***
urate                                          0.0375807  0.0088955   4.225 2.48e-05 ***
ldl_direct                                    -0.0057776  0.0372489  -0.155  0.87675    
c_reactive_protein                             0.0505941  0.0072055   7.022 2.82e-12 ***
aspartate_aminotransferase                    -0.0478666  0.0087086  -5.496 4.27e-08 ***
total_bilirubin                               -0.0052989  0.0071012  -0.746  0.45562    
apolipoprotein_b                               0.0111629  0.0255332   0.437  0.66201    
igf_1                                         -0.0134079  0.0066528  -2.015  0.04397 *  
glycated_haemoglobin_hb_a1c                    0.0311873  0.0069783   4.469 8.21e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.3141 on 2469 degrees of freedom
Multiple R-squared:  0.3059,    Adjusted R-squared:  0.2932 
F-statistic: 24.18 on 45 and 2469 DF,  p-value: < 2.2e-16

What about the multicollinearity?

vifs<- tibble(Measure= names(car::vif(m1)), 
              VIF= car::vif(m1)) %>%
       print()

A number of biochemistry and blood markers were showing as significant in a multiple linear model for PDFF. Together this model had an adjusted R^2 of 29.3%. However by looking at the variance inflation factors (VIFs) we can see that many of these are much larger than 10, meaning that multicollinearity is a serious issue in this model.

Stepwise Linear

We might try to apply a stepwise algorithm to the linear model, to potentially remove some of the redundant X variables.

stepwise_linear<- step(m1, trace=0)
summary(stepwise_linear)
vifs<- tibble(Measure= names(car::vif(stepwise_linear)), 
              VIF= car::vif(stepwise_linear)) %>%
       print()

In the stepwise linear model, we still have an adjusted R^2 of about 29%, but with only 26 out of the 46 variables. The coefficients of these are illustrated below, however it is clear from the VIFs that even the stepwise linear model has enough serious multicollinearity for us to doubt the resutls.

Feature Selection with Lasso Regularisation

Another option might be to apply lasso regularisation to select a useful set of variables and avoid multicollinearity.

# Apply lasso / regularised regression / feature selection
Y<- sett$PDFF
X<- sett %>% select(-PDFF) %>% as.matrix()

g1<- glmnet::glmnet(X, Y, family= "gaussian", alpha= 1)

# Coefficient priority matrix
mmat<- as.matrix(coef(g1))
mmat<- mmat[-1,] 
Zmat<- mmat==0

# Extract largest model where all p-values are significant
trialSet<- row.names(Zmat)[!Zmat[,13]] 
formula<-  as.formula(paste("PDFF ~",str_c(trialSet, collapse=" + "))) %>% print()
PDFF ~ red_blood_cell_erythrocyte_count + high_light_scatter_reticulocyte_count + 
    alanine_aminotransferase + triglycerides + urate + c_reactive_protein
m3<-       lm(formula, data= sett)
m3   %>%   summary()

Call:
lm(formula = formula, data = sett)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.5673 -0.1882 -0.0456  0.1323  1.7532 

Coefficients:
                                      Estimate Std. Error t value Pr(>|t|)    
(Intercept)                           1.309156   0.006389 204.892  < 2e-16 ***
red_blood_cell_erythrocyte_count      0.023925   0.007332   3.263  0.00112 ** 
high_light_scatter_reticulocyte_count 0.083482   0.007001  11.924  < 2e-16 ***
alanine_aminotransferase              0.056588   0.007193   7.867 5.36e-15 ***
triglycerides                         0.050112   0.007153   7.005 3.15e-12 ***
urate                                 0.033466   0.007543   4.437 9.54e-06 ***
c_reactive_protein                    0.060057   0.006692   8.974  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.3204 on 2508 degrees of freedom
Multiple R-squared:  0.266, Adjusted R-squared:  0.2643 
F-statistic: 151.5 on 6 and 2508 DF,  p-value: < 2.2e-16
m3   %>%   car::vif()
     red_blood_cell_erythrocyte_count high_light_scatter_reticulocyte_count 
                             1.316087                              1.200101 
             alanine_aminotransferase                         triglycerides 
                             1.266921                              1.252863 
                                urate                    c_reactive_protein 
                             1.393215                              1.096639 
m3terms<-  names(m3$coefficients)

Here 6 variables are highlighted, with an adjusted R^2 that is somewhat lower at 26%, but where there is no issue of multicollinearity.

Feature Extraction Principal Components Regression

Rather than feature selection, a method such as partial least squares can be used to gather correlated X variables together, in order to relate all variables to Y without collinearity. The first method here is Principal Components Regression, which is very similar to using the components of a PCA in a linear model

# Apply test-train split

set.seed(123)
idx <-         sample(1:nrow(sett), floor(nrow(sett)* 0.7))
train.data <-  sett[idx, ]
test.data <-   sett[-idx, ]

# Principal Components Regression

model <- train(
  PDFF~., data = train.data, method = "pcr",
  scale = TRUE,
  trControl = trainControl("cv", number = 5),
  tuneLength = 14
)
# Plot model RMSE vs different values of components
plot(model)


# Summarize the final model
summary(model$finalModel)
Data:   X dimension: 1760 45 
    Y dimension: 1760 1
Fit method: svdpc
Number of components considered: 12
TRAINING: % variance explained
          1 comps  2 comps  3 comps  4 comps  5 comps  6 comps  7 comps  8 comps  9 comps  10 comps  11 comps  12 comps
X           13.75    22.84    30.55    37.63    44.17    50.00    54.33    58.46    62.28     65.88     69.17     72.43
.outcome    21.23    22.63    22.73    23.27    23.37    23.41    23.56    23.57    23.62     23.92     24.00     24.10
# Make predictions
predictions <- model %>% predict(test.data)
# Model performance metrics
data.frame(
  RMSE = caret::RMSE(predictions, test.data$PDFF),
  Rsquare = caret::R2(predictions, test.data$PDFF)
)

Here PCR has extracted 12 components which together explain 72% of the variance in X, and which have an R^2 with Y of 25%

Feature Extraction with Partial Least Squares

Partial least squares is similar again, however rather than seeking to maximise the variance explained in X, components are extracted to maximise the shared correlation between X and Y.

### Computing partial least squares
# Build the model on training set
set.seed(1234)
model <- train(
  PDFF~., data = train.data, method = "pls",
  scale = TRUE,
  trControl = trainControl("cv", number = 10),
  tuneLength = 14
)
# Plot model RMSE vs different values of components
plot(model)

# Summarize the final model
summary(model$finalModel)
Data:   X dimension: 1760 45 
    Y dimension: 1760 1
Fit method: oscorespls
Number of components considered: 9
TRAINING: % variance explained
          1 comps  2 comps  3 comps  4 comps  5 comps  6 comps  7 comps  8 comps  9 comps
X           13.49    19.56    24.70    30.21    34.18    37.15    42.52    46.45    50.47
.outcome    24.56    27.28    28.67    29.00    29.26    29.43    29.47    29.51    29.55
# Make predictions
predictions <- model %>% predict(test.data)
# Model performance metrics
data.frame(
  RMSE = caret::RMSE(predictions, test.data$PDFF),
  Rsquare = caret::R2(predictions, test.data$PDFF)
)
NA

Here, PLS has extracted 9 components, which together explain 50% of the variation in X. These 9 components have an R^2 with Y of 29.2%

Examine how the variables load onto each component

We can extract the component loadings below to understand which variables are having the an influence in each component.

loadings<- model$finalModel$loadings %>%
           unclass() %>% as.matrix()

tl<- loadings %>% data.frame() %>%
                  rownames_to_column(var= "Measure") %>%
                  gather(Component, value, -Measure) %>% 
                  arrange(Measure, desc(abs(value))) %>%
                  group_by(Measure) %>% mutate(Order= order(desc(abs(value)))) %>%
                  #filter(abs(value) >= 0.2) %>%
                  filter(Order==1) %>%
                  mutate(Direction= ifelse(value > 0, "Positive", "Negative"))

g<- ggplot(tl, aes(x= value, y=Measure, fill= Direction)) +
           geom_col() +
           facet_wrap(~ Component, scales= "free", ncol= 2) +
           theme_bw() +
           theme(legend.position = "None",
                 text = element_text(size = 9),
                 strip.background = element_rect(fill= "grey30"),
                 strip.text = element_text(color="white", face= "bold"))
print(g)

PLS Components as inputs to a simpler linear model

Lastly, let’s plug the components calculated in the above PLS into an ordinary linear model to understand the impact of each component on PDFF.

scores<- model$finalModel$scores %>%
         unclass() %>%
         data.frame() 

newdf<- bind_cols(train.data, scores) 

m3<- lm(PDFF ~ Comp.1 + Comp.2 + Comp.3 + Comp.4 + Comp.5 + Comp.6 + Comp.7 + Comp.8 + Comp.9, data= newdf)
summary(m3)

Call:
lm(formula = PDFF ~ Comp.1 + Comp.2 + Comp.3 + Comp.4 + Comp.5 + 
    Comp.6 + Comp.7 + Comp.8 + Comp.9, data = newdf)

Residuals:
     Min       1Q   Median       3Q      Max 
-1.61942 -0.19375 -0.04196  0.12675  1.72864 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 1.310667   0.007594 172.591  < 2e-16 ***
Comp.1      0.077387   0.003133  24.701  < 2e-16 ***
Comp.2      0.047244   0.005746   8.222 3.87e-16 ***
Comp.3      0.032024   0.005466   5.859 5.55e-09 ***
Comp.4      0.017366   0.006038   2.876  0.00408 ** 
Comp.5      0.018101   0.007103   2.548  0.01090 *  
Comp.6      0.015680   0.007635   2.054  0.04015 *  
Comp.7      0.005771   0.006017   0.959  0.33759    
Comp.8      0.008243   0.008489   0.971  0.33165    
Comp.9      0.008507   0.008223   1.035  0.30102    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.3186 on 1750 degrees of freedom
Multiple R-squared:  0.2955,    Adjusted R-squared:  0.2919 
F-statistic: 81.55 on 9 and 1750 DF,  p-value: < 2.2e-16

As expected, the adjusted R^2 is sitting at the 29.2% mark. Here we see that only 6 components have p-values less than 0.05, and all coefficients are positive with Y. This gives us a good insight into which elements of biochemistry are most interesting when it comes to differences in PDFF. …

LS0tCnRpdGxlOiAiUGFydGlhbCBMZWFzdCBTcXVhcmVzOiBQREZGIGFuZCBCbG9vZC9CaW9jaGVtaXN0cnkiCm91dHB1dDogaHRtbF9ub3RlYm9vawpkYXRlOiAiMjAyMC0xMC0xMiIKYXV0aG9yOiAiQ2VsIE1jQ3JhY2tlbiIKLS0tCgoKIVtdKC9Vc2Vycy9DZWxlc3RlL0Rlc2t0b3AvQklPQ0hFTS5wbmcpCgojIyMgRG8gYmxvb2QgY291bnRzIG9yIGJpb2NoZW1pc3RyeSBzaG93IGEgcmVsYXRpb25zaGlwIHdpdGggUERGRj8KCkluIHRoaXMgbm90ZWJvb2sgSSB3aWxsIGRlbW9uc3RyYXRlIHNldmVyYWwgdGVjaG5pcXVlcyBmb3IgdW5kZXJzdGFuZGluZyB0aGUgcmVsYXRpb24gYmV0d2VlbiBhIHNpbmdsZSBjb250aW5vdXMgb3V0Y29tZSBhbmQgYSBzZXQgb2YgY29ycmVsYXRlZCB2YXJpYWJsZXMuClRoZSBkZXBlbmRlbnQgdmFyaWFibGUgaW4gdGhpcyBjYXNlIHN0dWR5IGlzIFByb3RvbiBEZW5zaXR5IEZhdCBGcmFjdGlvbiAoUERGRikgd2hpY2ggaXMgZGVmaW5lZCBhcyAKCj4gImEgbWVhc3VyZSBvZiB0aGUgcHJvcG9ydGlvbiBvZiBhIHRpc3N1ZSB3aGljaCBpcyBjb21wb3NlZCBvZiBmYXQiLCBpbiB0aGlzIGNhc2UgZGVyaXZlZCBmcm9tIE1SSSBpbWFnaW5nIG9mIHRoZSBsaXZlciAhW2Zyb20gaHR0cHM6Ly9wZXJzcGVjdHVtLmNvbS9tZWRpYS8xMzYxL3VuZGVyc3RhbmRpbmctcGRmZi5wZGZdLgoKIyMjIyBMb2FkIHRoZSBkYXRhIGFuZCBzdW1tYXJpc2UgdGhlIHZhcmlhYmxlcwpgYGB7ciwgd2FybmluZz0gRkFMU0UsIG1lc3NhZ2U9IEZBTFNFLCBpbmNsdWRlPSBUUlVFfQpzb3VyY2UoIlIvMF9zZXR1cC5SIikKCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkocGxzKQoKSVA8LSAgIHJlYWRSRFMoIkRlbW9TZXQyLnJkcyIpCgp0ZXN0czwtIG5hbWVzKElQKVsyNzo3MV0Kc2V0dDwtIElQICU+JSBzZWxlY3QoUERGRiwgYWxsX29mKHRlc3RzKSkgJT4lIAogICAgICAgICAgICAgICAgZmlsdGVyKGNvbXBsZXRlLmNhc2VzKC4pKSAlPiUKICAgICAgICAgICAgICAgIGRhdGEuZnJhbWUoKQpzazwtIHNraW0oc2V0dCkgJT4lIHNlbGVjdCh2YXJpYWJsZT0gc2tpbV92YXJpYWJsZSwgbWVhbj0gbnVtZXJpYy5tZWFuLCBtaW49IG51bWVyaWMucDAsIG1lZGlhbj0gbnVtZXJpYy5wNTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heD0gbnVtZXJpYy5wMTAwLCBudW1lcmljLmhpc3QpCnNrWywgMjo0XTwtIGFwcGx5KHNrWywgMjo0XSwgMiwgZnVuY3Rpb24oeCkgcm91bmQoeCwgMikpCnByaW50KHNrKQpgYGAKSW4gdGhlIHNldCB3aXRoIG1pc3NpbmcgZGF0YSByZW1vdmVkLCB3ZSBoYXZlIFBERkYgYW5kIGJpb2NoZW1pc3RyeSBkYXRhIGZvciAyNTE1IHBhcnRpY2lwYW50cy4gU2luY2UgUERGRiBpcyBoaWdobHkgc2tld2VkIGFuZCB0cnVuY2F0ZWQgYXQgemVybywgYSB0cmFuc2Zvcm1hdGlvbiBpcyBhcHBsaWVkIHRvIG1ha2UgaXQgZWFzaWVyIGZvciBsaW5lYXIgbW9kZWxzIHRvIG1ha2Ugc2Vuc2libGUgcHJlZGljdGlvbnMgb2YgUERGRi4KYGBge3J9CiMgU3RhbmRhcmRpc2UgdGhlIFggdmFyaWFibGVzCnNldHRbLC0xXSA8LSBzY2FsZShzZXR0WywgLTFdKQpQREZGX3JhdzwtICAgc2V0dCRQREZGCnNldHQkUERGRjwtICBzZXR0JFBERkZeMC4zCnNsb25nPC0gdGliYmxlKE1lYXN1cmU9IGMocmVwKCJQREZGIHJhdyIsIG5yb3coc2V0dCkpLHJlcCgiUERGRiBeIDAuMyIsIG5yb3coc2V0dCkpKSwKICAgICAgICAgICAgICAgVmFsdWU9IGMoUERGRl9yYXcsIHNldHQkUERGRikpCgpnPC0gZ2dwbG90KHNsb25nLCBhZXMoeD0gVmFsdWUsIGZpbGw9IGZjdF9yZXYoTWVhc3VyZSkpKSArCiAgIGdlb21faGlzdG9ncmFtKGJpbnMgPSA0MCwgY29sPSAiZ3JleTUwIiwgc2l6ZT0wLjIpKwogICBmYWNldF93cmFwKH5NZWFzdXJlLCBzY2FsZXM9ICJmcmVlIikgKwogICB0aGVtZV9idygpICsKICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsPSJncmV5MzAiKSwKICAgICAgICAgc3RyaXAudGV4dD0gZWxlbWVudF90ZXh0KGNvbG9yPSAid2hpdGUiLCBmYWNlPSJib2xkIiksCiAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJOb25lIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz0gYygicGFsZWdyZWVuMSIsImxpZ2h0Ymx1ZSIpKSArCiAgbGFicyh0aXRsZT0gIlJhdyBQREZGIHZzIFNjYWxlZCBQREZGIikKICAKcHJpbnQoZykKYGBgCiMjIyBBIE11bHRpcGxlIExpbmVhciBNb2RlbApUaGUgZmlyc3QgbWV0aG9kIHRoYXQgY29tZXMgdG8gbWluZCBpcyBhIG11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsLCB3aGVyZSB3ZSBtaWdodCBiZSB0ZW1wdGVkIHRvIHVzZSBhbGwgb3VyIGJpb2NoZW1zdHJ5IGRhdGEgYXMgWCB2YXJpYWJsZXMuIEhvd2V2ZXIsIGFzIHdlIHdpbGwgc2VlIGJlbG93LCB0aGlzIGlzIGJhZCBpZGVhIHdoZXJlIG91ciBYIHZhcmlhYmxlcyBhcmUgY29ycmVsYXRlZC4KYGBge3J9Cm0xPC0gbG0oUERGRn4uLCBkYXRhPSBzZXR0KSAKbTEgJT4lIHN1bW1hcnkoKSAlPiUgcHJpbnQoKQpgYGAKIyMjIFdoYXQgYWJvdXQgdGhlIG11bHRpY29sbGluZWFyaXR5PwpgYGB7cn0KdmlmczwtIHRpYmJsZShNZWFzdXJlPSBuYW1lcyhjYXI6OnZpZihtMSkpLCAKICAgICAgICAgICAgICBWSUY9IGNhcjo6dmlmKG0xKSkgJT4lCiAgICAgICBwcmludCgpCmBgYApBIG51bWJlciBvZiBiaW9jaGVtaXN0cnkgYW5kIGJsb29kIG1hcmtlcnMgd2VyZSBzaG93aW5nIGFzIHNpZ25pZmljYW50IGluIGEgbXVsdGlwbGUgbGluZWFyIG1vZGVsIGZvciBQREZGLiBUb2dldGhlciB0aGlzIG1vZGVsIGhhZCBhbiBhZGp1c3RlZCBSXjIgb2YgMjkuMyUuIEhvd2V2ZXIgYnkgbG9va2luZyBhdCB0aGUgdmFyaWFuY2UgaW5mbGF0aW9uIGZhY3RvcnMgKFZJRnMpIHdlIGNhbiBzZWUgdGhhdCBtYW55IG9mIHRoZXNlIGFyZSBtdWNoIGxhcmdlciB0aGFuIDEwLCBtZWFuaW5nIHRoYXQgbXVsdGljb2xsaW5lYXJpdHkgaXMgYSBzZXJpb3VzIGlzc3VlIGluIHRoaXMgbW9kZWwuCgojIyMgU3RlcHdpc2UgTGluZWFyCldlIG1pZ2h0IHRyeSB0byBhcHBseSBhIHN0ZXB3aXNlIGFsZ29yaXRobSB0byB0aGUgbGluZWFyIG1vZGVsLCB0byBwb3RlbnRpYWxseSByZW1vdmUgc29tZSBvZiB0aGUgcmVkdW5kYW50IFggdmFyaWFibGVzLgpgYGB7ciwgcmVzdWx0cz0gImhpZGUifQpzdGVwd2lzZV9saW5lYXI8LSBzdGVwKG0xLCB0cmFjZT0wKQpzdW1tYXJ5KHN0ZXB3aXNlX2xpbmVhcikKYGBgCmBgYHtyfQp2aWZzPC0gdGliYmxlKE1lYXN1cmU9IG5hbWVzKGNhcjo6dmlmKHN0ZXB3aXNlX2xpbmVhcikpLCAKICAgICAgICAgICAgICBWSUY9IGNhcjo6dmlmKHN0ZXB3aXNlX2xpbmVhcikpICU+JQogICAgICAgcHJpbnQoKQpgYGAKSW4gdGhlIHN0ZXB3aXNlIGxpbmVhciBtb2RlbCwgd2Ugc3RpbGwgaGF2ZSBhbiBhZGp1c3RlZCBSXjIgb2YgYWJvdXQgMjklLCBidXQgd2l0aCBvbmx5IDI2IG91dCBvZiB0aGUgNDYgdmFyaWFibGVzLiBUaGUgY29lZmZpY2llbnRzIG9mIHRoZXNlIGFyZSBpbGx1c3RyYXRlZCBiZWxvdywgaG93ZXZlciBpdCBpcyBjbGVhciBmcm9tIHRoZSBWSUZzIHRoYXQgZXZlbiB0aGUgc3RlcHdpc2UgbGluZWFyIG1vZGVsIGhhcyBlbm91Z2ggc2VyaW91cyBtdWx0aWNvbGxpbmVhcml0eSBmb3IgdXMgdG8gZG91YnQgdGhlIHJlc3V0bHMuCgpgYGB7cn0KbTI8LSBzdGVwd2lzZV9saW5lYXIgJT4lIHRpZHkoKSAlPiUgCiAgICAgICAgYXJyYW5nZShwLnZhbHVlKSAlPiUgZmlsdGVyKHAudmFsdWUgPCAwLjA1LCAhdGVybSAlbGlrZSUgIkludGVyY2VwdCIpICU+JQogICAgICAgIG11dGF0ZShNZWFzdXJlPSBmY3RfcmVvcmRlcih0ZXJtLCBlc3RpbWF0ZSksCiAgICAgICAgICAgICAgIERpcmVjdGlvbj0gaWZlbHNlKGVzdGltYXRlID4gMCwgIlBvc2l0aXZlIiwgIk5lZ2F0aXZlIikpIAoKIyAxOCBiaW9jaGVtIHRlc3RzIHdpdGggc2lnbmlmaWNhbnQgcC12YWx1ZXMKICAKZ2dwbG90KG0yLCBhZXMoeD0gZXN0aW1hdGUsIHk9IE1lYXN1cmUsIGZpbGw9IERpcmVjdGlvbikpICsKICBnZW9tX2NvbChzaXplPSAwLjMsIGNvbG9yPSJncmV5NDAiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPSBjKCJwYWxlZ3JlZW4xIiwgImluZGlhbnJlZDEiKSkgKwogIHRoZW1lX2J3KCkgKwogIGxhYnMoeD0gIkxpbmVhciByZWdyZXNzaW9uIGNvZWZmaWNpZW50IiwgdGl0bGU9ICJTaWduaWZpY2FudCBFZmZlY3RzIHZpYSBNdWx0aXBsZSBMaW5lYXIgUmVncmVzc2lvbiIpCmBgYAojIyMgRmVhdHVyZSBTZWxlY3Rpb24gd2l0aCBMYXNzbyBSZWd1bGFyaXNhdGlvbgpBbm90aGVyIG9wdGlvbiBtaWdodCBiZSB0byBhcHBseSBsYXNzbyByZWd1bGFyaXNhdGlvbiB0byBzZWxlY3QgYSB1c2VmdWwgc2V0IG9mIHZhcmlhYmxlcyBhbmQgYXZvaWQgbXVsdGljb2xsaW5lYXJpdHkuCmBgYHtyLCBtZXNzYWdlPSBGQUxTRSwgbWVzc2FnZT0gRkFMU0V9CiMgQXBwbHkgbGFzc28gLyByZWd1bGFyaXNlZCByZWdyZXNzaW9uIC8gZmVhdHVyZSBzZWxlY3Rpb24KWTwtIHNldHQkUERGRgpYPC0gc2V0dCAlPiUgc2VsZWN0KC1QREZGKSAlPiUgYXMubWF0cml4KCkKCmcxPC0gZ2xtbmV0OjpnbG1uZXQoWCwgWSwgZmFtaWx5PSAiZ2F1c3NpYW4iLCBhbHBoYT0gMSkKCiMgQ29lZmZpY2llbnQgcHJpb3JpdHkgbWF0cml4Cm1tYXQ8LSBhcy5tYXRyaXgoY29lZihnMSkpCm1tYXQ8LSBtbWF0Wy0xLF0gClptYXQ8LSBtbWF0PT0wCgojIEV4dHJhY3QgbGFyZ2VzdCBtb2RlbCB3aGVyZSBhbGwgcC12YWx1ZXMgYXJlIHNpZ25pZmljYW50CnRyaWFsU2V0PC0gcm93Lm5hbWVzKFptYXQpWyFabWF0WywxM11dIApmb3JtdWxhPC0gIGFzLmZvcm11bGEocGFzdGUoIlBERkYgfiIsc3RyX2ModHJpYWxTZXQsIGNvbGxhcHNlPSIgKyAiKSkpICU+JSBwcmludCgpCm0zPC0gICAgICAgbG0oZm9ybXVsYSwgZGF0YT0gc2V0dCkKbTMgICAlPiUgICBzdW1tYXJ5KCkKbTMgICAlPiUgICBjYXI6OnZpZigpCm0zdGVybXM8LSAgbmFtZXMobTMkY29lZmZpY2llbnRzKQpgYGAKSGVyZSA2IHZhcmlhYmxlcyBhcmUgaGlnaGxpZ2h0ZWQsIHdpdGggYW4gYWRqdXN0ZWQgUl4yIHRoYXQgaXMgc29tZXdoYXQgbG93ZXIgYXQgMjYlLCBidXQgd2hlcmUgdGhlcmUgaXMgbm8gaXNzdWUgb2YgbXVsdGljb2xsaW5lYXJpdHkuCgojIyMgRmVhdHVyZSBFeHRyYWN0aW9uIFByaW5jaXBhbCBDb21wb25lbnRzIFJlZ3Jlc3Npb24KUmF0aGVyIHRoYW4gZmVhdHVyZSBzZWxlY3Rpb24sIGEgbWV0aG9kIHN1Y2ggYXMgcGFydGlhbCBsZWFzdCBzcXVhcmVzIGNhbiBiZSB1c2VkIHRvIGdhdGhlciBjb3JyZWxhdGVkIFggdmFyaWFibGVzIHRvZ2V0aGVyLCBpbiBvcmRlciB0byByZWxhdGUgYWxsIHZhcmlhYmxlcyB0byBZIHdpdGhvdXQgY29sbGluZWFyaXR5LiBUaGUgZmlyc3QgbWV0aG9kIGhlcmUgaXMgUHJpbmNpcGFsIENvbXBvbmVudHMgUmVncmVzc2lvbiwgd2hpY2ggaXMgdmVyeSBzaW1pbGFyIHRvIHVzaW5nIHRoZSBjb21wb25lbnRzIG9mIGEgUENBIGluIGEgbGluZWFyIG1vZGVsCmBgYHtyfQojIEFwcGx5IHRlc3QtdHJhaW4gc3BsaXQKCnNldC5zZWVkKDEyMykKaWR4IDwtICAgICAgICAgc2FtcGxlKDE6bnJvdyhzZXR0KSwgZmxvb3IobnJvdyhzZXR0KSogMC43KSkKdHJhaW4uZGF0YSA8LSAgc2V0dFtpZHgsIF0KdGVzdC5kYXRhIDwtICAgc2V0dFstaWR4LCBdCgojIFByaW5jaXBhbCBDb21wb25lbnRzIFJlZ3Jlc3Npb24KCm1vZGVsIDwtIHRyYWluKAogIFBERkZ+LiwgZGF0YSA9IHRyYWluLmRhdGEsIG1ldGhvZCA9ICJwY3IiLAogIHNjYWxlID0gVFJVRSwKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2woImN2IiwgbnVtYmVyID0gNSksCiAgdHVuZUxlbmd0aCA9IDE0CikKIyBQbG90IG1vZGVsIFJNU0UgdnMgZGlmZmVyZW50IHZhbHVlcyBvZiBjb21wb25lbnRzCnBsb3QobW9kZWwpCmBgYApgYGB7cn0KIyBTdW1tYXJpemUgdGhlIGZpbmFsIG1vZGVsCnN1bW1hcnkobW9kZWwkZmluYWxNb2RlbCkKYGBgCmBgYHtyfQojIE1ha2UgcHJlZGljdGlvbnMKcHJlZGljdGlvbnMgPC0gbW9kZWwgJT4lIHByZWRpY3QodGVzdC5kYXRhKQojIE1vZGVsIHBlcmZvcm1hbmNlIG1ldHJpY3MKZGF0YS5mcmFtZSgKICBSTVNFID0gY2FyZXQ6OlJNU0UocHJlZGljdGlvbnMsIHRlc3QuZGF0YSRQREZGKSwKICBSc3F1YXJlID0gY2FyZXQ6OlIyKHByZWRpY3Rpb25zLCB0ZXN0LmRhdGEkUERGRikKKQpgYGAKSGVyZSBQQ1IgaGFzIGV4dHJhY3RlZCAxMiBjb21wb25lbnRzIHdoaWNoIHRvZ2V0aGVyIGV4cGxhaW4gNzIlIG9mIHRoZSB2YXJpYW5jZSBpbiBYLCBhbmQgd2hpY2ggaGF2ZSBhbiBSXjIgd2l0aCBZIG9mIDI1JQoKIyMjIEZlYXR1cmUgRXh0cmFjdGlvbiB3aXRoIFBhcnRpYWwgTGVhc3QgU3F1YXJlcwpQYXJ0aWFsIGxlYXN0IHNxdWFyZXMgaXMgc2ltaWxhciBhZ2FpbiwgaG93ZXZlciByYXRoZXIgdGhhbiBzZWVraW5nIHRvIG1heGltaXNlIHRoZSB2YXJpYW5jZSBleHBsYWluZWQgaW4gWCwgY29tcG9uZW50cyBhcmUgZXh0cmFjdGVkIHRvIG1heGltaXNlIHRoZSBzaGFyZWQgY29ycmVsYXRpb24gYmV0d2VlbiBYIGFuZCBZLgpgYGB7cn0KIyMjIENvbXB1dGluZyBwYXJ0aWFsIGxlYXN0IHNxdWFyZXMKIyBCdWlsZCB0aGUgbW9kZWwgb24gdHJhaW5pbmcgc2V0CnNldC5zZWVkKDEyMzQpCm1vZGVsIDwtIHRyYWluKAogIFBERkZ+LiwgZGF0YSA9IHRyYWluLmRhdGEsIG1ldGhvZCA9ICJwbHMiLAogIHNjYWxlID0gVFJVRSwKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2woImN2IiwgbnVtYmVyID0gMTApLAogIHR1bmVMZW5ndGggPSAxNAopCiMgUGxvdCBtb2RlbCBSTVNFIHZzIGRpZmZlcmVudCB2YWx1ZXMgb2YgY29tcG9uZW50cwpwbG90KG1vZGVsKQpgYGAKYGBge3J9CiMgU3VtbWFyaXplIHRoZSBmaW5hbCBtb2RlbApzdW1tYXJ5KG1vZGVsJGZpbmFsTW9kZWwpCmBgYAoKYGBge3J9CiMgTWFrZSBwcmVkaWN0aW9ucwpwcmVkaWN0aW9ucyA8LSBtb2RlbCAlPiUgcHJlZGljdCh0ZXN0LmRhdGEpCiMgTW9kZWwgcGVyZm9ybWFuY2UgbWV0cmljcwpkYXRhLmZyYW1lKAogIFJNU0UgPSBjYXJldDo6Uk1TRShwcmVkaWN0aW9ucywgdGVzdC5kYXRhJFBERkYpLAogIFJzcXVhcmUgPSBjYXJldDo6UjIocHJlZGljdGlvbnMsIHRlc3QuZGF0YSRQREZGKQopCgpgYGAKSGVyZSwgUExTIGhhcyBleHRyYWN0ZWQgOSBjb21wb25lbnRzLCB3aGljaCB0b2dldGhlciBleHBsYWluIDUwJSBvZiB0aGUgdmFyaWF0aW9uIGluIFguICBUaGVzZSA5IGNvbXBvbmVudHMgaGF2ZSBhbiBSXjIgd2l0aCBZIG9mIDI5LjIlCgojIyMgRXhhbWluZSBob3cgdGhlIHZhcmlhYmxlcyBsb2FkIG9udG8gZWFjaCBjb21wb25lbnQKV2UgY2FuIGV4dHJhY3QgdGhlIGNvbXBvbmVudCBsb2FkaW5ncyBiZWxvdyB0byB1bmRlcnN0YW5kIHdoaWNoIHZhcmlhYmxlcyBhcmUgaGF2aW5nIHRoZSBhbiBpbmZsdWVuY2UgaW4gZWFjaCBjb21wb25lbnQuIApgYGB7ciwgZmlnLmhlaWdodD0gM30KbG9hZGluZ3M8LSBtb2RlbCRmaW5hbE1vZGVsJGxvYWRpbmdzICU+JQogICAgICAgICAgIHVuY2xhc3MoKSAlPiUgYXMubWF0cml4KCkKCnRsPC0gbG9hZGluZ3MgJT4lIGRhdGEuZnJhbWUoKSAlPiUKICAgICAgICAgICAgICAgICAgcm93bmFtZXNfdG9fY29sdW1uKHZhcj0gIk1lYXN1cmUiKSAlPiUKICAgICAgICAgICAgICAgICAgZ2F0aGVyKENvbXBvbmVudCwgdmFsdWUsIC1NZWFzdXJlKSAlPiUgCiAgICAgICAgICAgICAgICAgIGFycmFuZ2UoTWVhc3VyZSwgZGVzYyhhYnModmFsdWUpKSkgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KE1lYXN1cmUpICU+JSBtdXRhdGUoT3JkZXI9IG9yZGVyKGRlc2MoYWJzKHZhbHVlKSkpKSAlPiUKICAgICAgICAgICAgICAgICAgI2ZpbHRlcihhYnModmFsdWUpID49IDAuMikgJT4lCiAgICAgICAgICAgICAgICAgIGZpbHRlcihPcmRlcj09MSkgJT4lCiAgICAgICAgICAgICAgICAgIG11dGF0ZShEaXJlY3Rpb249IGlmZWxzZSh2YWx1ZSA+IDAsICJQb3NpdGl2ZSIsICJOZWdhdGl2ZSIpKQoKZzwtIGdncGxvdCh0bCwgYWVzKHg9IHZhbHVlLCB5PU1lYXN1cmUsIGZpbGw9IERpcmVjdGlvbikpICsKICAgICAgICAgICBnZW9tX2NvbCgpICsKICAgICAgICAgICBmYWNldF93cmFwKH4gQ29tcG9uZW50LCBzY2FsZXM9ICJmcmVlIiwgbmNvbD0gMikgKwogICAgICAgICAgIHRoZW1lX2J3KCkgKwogICAgICAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJOb25lIiwKICAgICAgICAgICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA5KSwKICAgICAgICAgICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGw9ICJncmV5MzAiKSwKICAgICAgICAgICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yPSJ3aGl0ZSIsIGZhY2U9ICJib2xkIikpCnByaW50KGcpCgpgYGAKIyMjIFBMUyBDb21wb25lbnRzIGFzIGlucHV0cyB0byBhIHNpbXBsZXIgbGluZWFyIG1vZGVsCkxhc3RseSwgbGV0J3MgcGx1ZyB0aGUgY29tcG9uZW50cyBjYWxjdWxhdGVkIGluIHRoZSBhYm92ZSBQTFMgaW50byBhbiBvcmRpbmFyeSBsaW5lYXIgbW9kZWwgdG8gdW5kZXJzdGFuZCB0aGUgaW1wYWN0IG9mIGVhY2ggY29tcG9uZW50IG9uIFBERkYuCmBgYHtyfQpzY29yZXM8LSBtb2RlbCRmaW5hbE1vZGVsJHNjb3JlcyAlPiUKICAgICAgICAgdW5jbGFzcygpICU+JQogICAgICAgICBkYXRhLmZyYW1lKCkgCgpuZXdkZjwtIGJpbmRfY29scyh0cmFpbi5kYXRhLCBzY29yZXMpIAoKbTM8LSBsbShQREZGIH4gQ29tcC4xICsgQ29tcC4yICsgQ29tcC4zICsgQ29tcC40ICsgQ29tcC41ICsgQ29tcC42ICsgQ29tcC43ICsgQ29tcC44ICsgQ29tcC45LCBkYXRhPSBuZXdkZikKc3VtbWFyeShtMykKYGBgCkFzIGV4cGVjdGVkLCB0aGUgYWRqdXN0ZWQgUl4yIGlzIHNpdHRpbmcgYXQgdGhlIDI5LjIlIG1hcmsuIEhlcmUgd2Ugc2VlIHRoYXQgb25seSA2IGNvbXBvbmVudHMgaGF2ZSBwLXZhbHVlcyBsZXNzIHRoYW4gMC4wNSwgYW5kIGFsbCBjb2VmZmljaWVudHMgYXJlIHBvc2l0aXZlIHdpdGggWS4gIFRoaXMgZ2l2ZXMgdXMgYSBnb29kIGluc2lnaHQgaW50byB3aGljaCBlbGVtZW50cyBvZiBiaW9jaGVtaXN0cnkgYXJlIG1vc3QgaW50ZXJlc3Rpbmcgd2hlbiBpdCBjb21lcyB0byBkaWZmZXJlbmNlcyBpbiBQREZGLgouLi4KCgouLi4KCgouLi4KCg==