1 Giới thiệu

Mixed model là một dạng mô hình hồi quy cho phép khảo sát đồng thời hiệu ứng chính (fixed effect) và hiệu ứng ngẫu nhiên (random effect) với nhiều ứng dụng trong nghiên cứu y học. Phương pháp này được hỗ trợ rất sâu và rộng trong ngôn ngữ R,đây là một lợi thế. Tuy nhiên, đây cũng chính là trở ngại quan trọng - vì có quá nhiều R packages để dựng mixed model, mỗi loại dùng cú pháp khác nhau, và cho ra kết quả với nội dung và cấu trúc khác nhau (về class, về các thành phần tham số, hình thức trình bày, suy diễn thống kê…), đa phần rối rắm khó diễn giải và bắt buộc dùng những phương pháp hậu kiểm, phân tích chuyên biệt và tương thích với mỗi package.

Ngày 21/10 vừa qua, package broom.mixed được công bố trên CRAN, và Nhi sẽ giới thiệu với các bạn về nó trong bài này. Đây là một công cụ tiện lợi, cho phép chúng ta diễn giải và khai thác nội dung của những mô hình Mixed model dựng bằng bất cứ packages nào theo cùng một cách thức. Như vậy, công dụng của broom.mixed tương tự như package broom mà Nhi từng giới thiệu, nhưng chuyên biệt cho mixed model.

Những tính năng tiện dụng của broom.mixed sẽ lần lượt được minh họa dưới đây

2 Phạm vi ứng dụng

Package broom.mixed tương thích với tất cả những định dạng mixed model từng xuất hiện trong ngôn ngữ R, bao gồm phái frequentist với họ mô hình lmer/glmer (package lme4), lme/gls/nlme (package nlme), glmmTMB glmmadmb (package glmmTMB, glmmADMB), và phái Bayes: brmsfit (package brms), stanreg (package rstanarm), MCMCglmm, thậm chí nó đọc được cả loại mô hình GAMLSS (package gamlss) vốn là kiểu định dạng mixed model khó chịu nhất.

Công dụng của package broom.mixed liên quan đến 3 phương pháp (function) :

  1. glance: tóm tắt phẩm chất của mixed model, nó cung cấp thông tin khái quát về sự phù hợp của model với dữ liệu, với những tiêu chí như AIC, LOOIC, R2.

  2. augment: phân tích sâu về tính chính xác của mô hình, cụ thể là sai số tồn lưu (residual error), giá trị dự báo (predicted values) trên chính dữ liệu hiện hành hay dữ liệu gốc, và các chỉ số khác (influence…).Dữ liệu xuất ra còn có thể dùng để trình bày biểu đồ để so sánh giá trị dự báo và quan sát thực tế.

  3. Cuối cùng, phương pháp tidy; cho phép trình bày nội dung mô hình nhằm phục vụ cho suy diễn thống kê (estimate, s.e, CI (cho frequentist) hay HDI (phái Bayes), p_values Wald test…), đặc biệt người dùng có thể tùy chỉnh ngưỡng trên/dưới của CI, tính CI bằng bootstrap,chọn Mean hay median cho estimate… Những thông số này được chuyển thành tibble, nên có thể được dùng để vẽ biểu đồ dễ dàng bằng ggplot2

package object tidy glance augment effects.fixed effects.ran_vals effects.ran_pars effects.ran_coefs confint.â…Ww.aldâ.. confint.â..profileâ.. confint.â..bootâ.. component.zi component.disp covstruct
lme4 glmer y y y y y y y NA y NA NA NA ?
lme4 lmer y y y y y y y NA y NA NA NA ?
nlme lme y y y y y y y NA n NA NA ? ?
nlme gls y y y y NA NA NA NA n NA NA ? ?
nlme nlme y y y y y n y NA n NA NA ? ?
glmmTMB glmmTMB y y y y y y n NA NA y y ?
glmmADMB glmmadmb y y y y y y n NA NA y ? ?
brms brmsfit y y y y y y n NA NA y ? ?
rstanarm stanreg y y y y y y n NA NA NA NA ?
MCMCglmm MCMCglmm y y y y y y n NA NA ? ? ?
TMB TMB y n n n n n n NA NA NA NA ?
INLA n n n n n n n NA NA ? ? ?

3 Thí dụ minh họa

library(tidyverse)
library(broom.mixed)

Đầu tiên, Nhi sẽ thử broom.mixed trên 2 package thông dụng nhất cho mixed model, đó là nlme và lme4. Với package nlme, ta thử dựng một mô hình mixed model với random slope cho dataset “sleep study” quen thuộc;

Như các bạn thấy: nội dung nguyên thủy của mô hình nlme bao gồm 3 phần; hiệu ứng random được khảo sát dưới dạng within group residuals, còn kết quả hiệu ứng chính gần giống như mô hình glm. Ngoài ra còn có các tiêu chí phẩm chất mô hình như AIC, BIC và LogLikelihood. Kết quả này có quá nhiều chi tiết thừa thải, không có ích và thậm chí khó hiểu đối với nhiều bạn.

data("sleepstudy", package="lme4")

nlme_model <- nlme::lme(Reaction ~ Days, random =~ Days|Subject, data=sleepstudy)

summary(nlme_model)
## Linear mixed-effects model fit by REML
##  Data: sleepstudy 
##        AIC      BIC    logLik
##   1755.628 1774.719 -871.8141
## 
## Random effects:
##  Formula: ~Days | Subject
##  Structure: General positive-definite, Log-Cholesky parametrization
##             StdDev    Corr  
## (Intercept) 24.740241 (Intr)
## Days         5.922103 0.066 
## Residual    25.591843       
## 
## Fixed effects: Reaction ~ Days 
##                 Value Std.Error  DF  t-value p-value
## (Intercept) 251.40510  6.824516 161 36.83853       0
## Days         10.46729  1.545783 161  6.77151       0
##  Correlation: 
##      (Intr)
## Days -0.138
## 
## Standardized Within-Group Residuals:
##         Min          Q1         Med          Q3         Max 
## -3.95355735 -0.46339976  0.02311783  0.46339621  5.17925089 
## 
## Number of Observations: 180
## Number of Groups: 18

Với hàm tidy, ta có thể tóm tắt những nội dung chính của mô hình như sau:

tidy(nlme_model)
## # A tibble: 6 x 8
##   effect  group   term        estimate std.error    df statistic   p.value
##   <chr>   <chr>   <chr>          <dbl>     <dbl> <dbl>     <dbl>     <dbl>
## 1 fixed   fixed   (Intercept) 251.          6.82   161     36.8   2.38e-80
## 2 fixed   fixed   Days         10.5         1.55   161      6.77  2.26e-10
## 3 ran_pa~ Subject sd_(Interc~  24.7        NA       NA     NA    NA       
## 4 ran_pa~ Subject cor_Days.(~   0.0656     NA       NA     NA    NA       
## 5 ran_pa~ Subject sd_Days       5.92       NA       NA     NA    NA       
## 6 ran_pa~ Residu~ sd_Observa~  25.6        NA       NA     NA    NA

Kết quả trình bày trong 1 bảng gọn gàng: các tham số của mô hình được chia thành nhóm: effect là nhóm hiệu ứng với Fixed = hiệu ứng chính, ran_pars là tham số cho hiệu ứng ngẫu nhiên, các hiệu ứng này lại được phân bố cho quần thể (fixed), Subject (cá thể) và residual: sai số không giải thích được, tương ứng với các terms trong mô hình bao gồm Intercept, biến độc lập, sd của random effects, correlation và rsd theo cá thể. Với fixed effect, kết quả gồm Mean, SE, df, kiểm định Wald và p.values.

Hình thức trình bày này dễ hiểu, trong sáng hơn nhiều so với nội dung gốc.

Bạn có thể thêm bớt arguments để lựa chọn: Chỉ xuất kết quả cho hiệu ứng chính, và thêm khoảng tin cậy 90% (hay một ngưỡng cao hơn, nếu bạn thích)

tidy(nlme_model, effects = "fixed", conf.int = T)
## # A tibble: 2 x 8
##   term      estimate std.error    df statistic  p.value conf.low conf.high
##   <chr>        <dbl>     <dbl> <dbl>     <dbl>    <dbl>    <dbl>     <dbl>
## 1 (Interce~    251.       6.82   161     36.8  2.38e-80   238.       265. 
## 2 Days          10.5      1.55   161      6.77 2.26e-10     7.41      13.5

Không chỉ khảo sát hiệu ứng ngẫu nhiên dưới hình thức parameters, hàm tidy còn cho phép ước lượng giá trị random effect cho từng cá thể:

tidy(nlme_model, 
     effects = "ran_vals")%>%head()
## # A tibble: 6 x 3
##   level term        estimate
##   <chr> <chr>          <dbl>
## 1 308   (Intercept)     2.26
## 2 309   (Intercept)   -40.4 
## 3 310   (Intercept)   -39.0 
## 4 330   (Intercept)    23.7 
## 5 331   (Intercept)    22.3 
## 6 332   (Intercept)     9.04

Hàm glance sẽ xuất riêng giá trị LogLikelihood, AIC, BIC và sigma

glance(nlme_model)
## # A tibble: 1 x 4
##   sigma logLik   AIC   BIC
##   <dbl>  <dbl> <dbl> <dbl>
## 1  25.6  -872. 1756. 1775.

Hàm augment cho phép xuất giá trị dự báo cho chính dữ liệu gốc, hoặc áp dụng cho một dataset mớivà xuất kết quả dưới dạng dataframe: tương tự như hàm predict

augment(nlme_model, newdata=sample_frac(sleepstudy,0.2))%>%head()
## # A tibble: 6 x 5
##   .rownames Reaction  Days Subject .fitted
##   <chr>        <dbl> <dbl> <fct>     <dbl>
## 1 125           272.     4 351        286.
## 2 155           268.     4 370        287.
## 3 1             250.     0 308        254.
## 4 177           334.     6 372        334.
## 5 106           270.     5 349        284.
## 6 59            330.     8 332        342.

Dữ liệu này cho phép vẽ những biểu đồ như sau để kiểm tra trực quan hoạt động của mô hình:

augment(nlme_model, newdata=sample_frac(sleepstudy,0.2))%>%
  gather(Reaction,.fitted,key="Type",value="Score")%>%
  ggplot()+
  geom_density(aes(x=Score,fill=Type),alpha=0.5)+
  theme_bw()

augment(nlme_model, newdata=sample_frac(sleepstudy,0.2))%>%
  gather(Reaction,.fitted,key="Type",value="Score")%>%
  ggplot()+
  geom_smooth(aes(x=Days,y=Score,fill=Type,col=Type),alpha=0.2,method="glm")+
  theme_bw()

Bây giờ chúng ta qua package lme4, và thử dựng một logistic mixed model cho dataset “cbpp”

Như ta thấy, nội dung nguyên thủy của mô hình lme4 được trình bày hoàn toàn khác so với mô hình nlme, các tham số fixed effect được khảo sát bằng SE và test Z, trong khi random effect được trình bày dưới dạng variance và SD của intercept. Ngoài ra còn có thêm một correlation matrix cho fixed effects

data("cbpp", package="lme4")

lme4_logistic <- lme4::glmer(cbind(incidence, size - incidence) ~ period + (1 | herd),
               data = cbpp, family = binomial)

summary(lme4_logistic)
## Generalized linear mixed model fit by maximum likelihood (Laplace
##   Approximation) [glmerMod]
##  Family: binomial  ( logit )
## Formula: cbind(incidence, size - incidence) ~ period + (1 | herd)
##    Data: cbpp
## 
##      AIC      BIC   logLik deviance df.resid 
##    194.1    204.2    -92.0    184.1       51 
## 
## Scaled residuals: 
##     Min      1Q  Median      3Q     Max 
## -2.3816 -0.7889 -0.2026  0.5142  2.8791 
## 
## Random effects:
##  Groups Name        Variance Std.Dev.
##  herd   (Intercept) 0.4123   0.6421  
## Number of obs: 56, groups:  herd, 15
## 
## Fixed effects:
##             Estimate Std. Error z value Pr(>|z|)    
## (Intercept)  -1.3983     0.2312  -6.048 1.47e-09 ***
## period2      -0.9919     0.3032  -3.272 0.001068 ** 
## period3      -1.1282     0.3228  -3.495 0.000474 ***
## period4      -1.5797     0.4220  -3.743 0.000182 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Correlation of Fixed Effects:
##         (Intr) perid2 perid3
## period2 -0.363              
## period3 -0.340  0.280       
## period4 -0.260  0.213  0.198

Ta thử dùng hàm tidy, với 2 yêu cầu phụ:

  1. Áp dụng hàm exponential để tính Odds-ratios.

  2. Xuất 97.5% CI bằng phương pháp bootstrap .

Quy trình bootstrap mất một ít thời gian, sau đây là kết quả:

Như ta thấy, hàm tidy đã bỏ hết những chi tiết thừa, quy đồng nội dung mô hình về cùng một hình thức trình bày, cấu trúc bảng tương tự như trong mô hình nlme, các terms được phân thành loại effect và group; với giá trị đã được hoán chuyển thành Odds-ratio, Z-values giữ nguyên,estimate tương ứng vớitrung bình của Odds-ratio, SE và 97.5% CI của Odds-ratio được xác định bằng bootstrap. Kết quả này sẵn sàng để đưa vào báo cáo.

tidy(lme4_logistic,
     exponentiate=TRUE, 
     conf.int = T, 
     conf.level = 0.975, 
     conf.method ="boot")
## # A tibble: 5 x 9
##   effect group term  estimate std.error statistic  p.value conf.low
##   <chr>  <chr> <chr>    <dbl>     <dbl>     <dbl>    <dbl>    <dbl>
## 1 fixed  <NA>  (Int~    0.247    0.0571     -6.05  1.47e-9   0.139 
## 2 fixed  <NA>  peri~    0.371    0.112      -3.27  1.07e-3   0.189 
## 3 fixed  <NA>  peri~    0.324    0.104      -3.49  4.74e-4   0.138 
## 4 fixed  <NA>  peri~    0.206    0.0870     -3.74  1.82e-4   0.0677
## 5 ran_p~ herd  sd__~    0.642   NA          NA    NA         0.167 
## # ... with 1 more variable: conf.high <dbl>

Ta thử hàm glance để khảo sát phẩm chất mô hình:

glance(lme4_logistic)
## # A tibble: 1 x 6
##   sigma logLik   AIC   BIC deviance df.residual
##   <dbl>  <dbl> <dbl> <dbl>    <dbl>       <int>
## 1     1  -92.0  194.  204.     73.5          51

Và hàm augment để ước tính giá trị dự báo trên chính dữ liệu gốc:

augment(lme4_logistic,data=cbpp)%>%head()
##   herd incidence size period    .fitted     .resid    .fixed        .mu
## 1    1         2   14      1 -0.8087134 -1.4377078 -1.398343 0.30816472
## 2    1         3   12      2 -1.8006384  0.9884720 -2.390268 0.14177337
## 3    1         4    9      3 -1.9369296  2.3566883 -2.526559 0.12598556
## 4    1         0    5      4 -2.3884588 -0.9370227 -2.978088 0.08405701
## 5    2         3   22      1 -1.6974362 -0.2431732 -1.398343 0.15480041
## 6    2         1   18      2 -2.6893612 -0.1428294 -2.390268 0.06360406
##   .offset  .sqrtXwt  .sqrtrwt .weights     .wtres       .eta
## 1       0 1.7276542  8.103473       14 -1.3395656 -0.8087134
## 2       0 1.2083394  9.930984       12  1.0747970 -1.8006384
## 3       0 0.9954992  9.040690        9  2.8790881 -1.9369296
## 4       0 0.6204492  8.058678        5 -0.6773884 -2.3884588
## 5       0 1.6965905 12.967183       22 -0.2390730 -1.6974362
## 6       0 1.0354006 17.384575       18 -0.1399198 -2.6893612

Bây giờ chúng ta sẽ thử áp dụng broom.mixed cho mô hình Bayes, cụ thể là 2 package brms và rstanarm. Để tiết kiệm thời gian, Nhi sẽ không thực sự dùng brms nhưng tải dữ liệu một mô hình brms có sẵn trong package broom.mixed:

load(system.file("extdata", "brms_example.rda", package="broom.mixed"))

Đây là một mô hình mixed model với crossed random effect trên dataset mtcars

fit <- brms_crossedRE

fit$fit
## Inference for Stan model: 7fd04982032e6915d9435dfb9d716706.
## 2 chains, each with iter=100; warmup=50; thin=1; 
## post-warmup draws per chain=50, total post-warmup draws=100.
## 
##                           mean se_mean   sd    2.5%     25%    50%    75%
## b_Intercept              34.41    0.72 3.87   26.16   32.23  34.25  36.78
## b_wt                     -4.25    0.30 1.26   -6.90   -4.95  -4.25  -3.48
## sd_cyl__Intercept         4.88    0.30 2.98    0.76    2.47   4.38   6.62
## sd_gear__Intercept        4.70    0.78 3.23    0.27    2.43   4.13   6.47
## sd_gear__wt               1.72    0.46 1.45    0.05    0.71   1.29   2.36
## cor_gear__Intercept__wt  -0.38    0.07 0.57   -0.99   -0.83  -0.59  -0.07
## sigma                     2.53    0.07 0.40    1.77    2.25   2.47   2.77
## r_cyl[4,Intercept]        1.98    0.68 2.36   -2.30    0.33   2.15   3.58
## r_cyl[6,Intercept]       -0.92    0.64 2.27   -5.30   -2.21  -0.96   0.33
## r_cyl[8,Intercept]       -2.21    0.47 2.23   -6.95   -3.76  -1.90  -0.51
## r_gear[3,Intercept]      -3.68    0.80 4.43  -12.39   -7.55  -2.55  -0.19
## r_gear[4,Intercept]       0.99    0.34 2.52   -3.68   -0.66   0.85   2.44
## r_gear[5,Intercept]      -0.59    0.63 2.69   -6.62   -1.75  -0.11   1.00
## r_gear[3,wt]              0.96    0.31 1.27   -0.62    0.01   0.56   1.71
## r_gear[4,wt]             -0.30    0.18 1.01   -2.45   -0.98  -0.14   0.24
## r_gear[5,wt]             -0.29    0.26 1.18   -2.45   -0.92  -0.24   0.11
## lp__                    -98.19    1.27 3.85 -105.47 -101.12 -97.83 -95.01
##                          97.5% n_eff Rhat
## b_Intercept              41.78    29 1.12
## b_wt                     -1.94    17 1.12
## sd_cyl__Intercept        11.69   100 1.01
## sd_gear__Intercept       12.94    17 1.22
## sd_gear__wt               4.84    10 1.36
## cor_gear__Intercept__wt   0.85    64 1.00
## sigma                     3.27    32 1.11
## r_cyl[4,Intercept]        7.41    12 1.15
## r_cyl[6,Intercept]        2.87    13 1.18
## r_cyl[8,Intercept]        1.24    23 1.09
## r_gear[3,Intercept]       2.51    31 1.21
## r_gear[4,Intercept]       5.78    55 1.00
## r_gear[5,Intercept]       3.32    18 1.06
## r_gear[3,wt]              4.34    16 1.25
## r_gear[4,wt]              1.64    33 1.04
## r_gear[5,wt]              2.31    20 1.10
## lp__                    -92.24     9 1.57
## 
## Samples were drawn using NUTS(diag_e) at Tue Oct 16 08:42:29 2018.
## For each parameter, n_eff is a crude measure of effective sample size,
## and Rhat is the potential scale reduction factor on split chains (at 
## convergence, Rhat=1).

Khác với lme4 hay nlme, brms thực chất áp dụng suy diễn Bayes để tạo ra những chuỗi MCMC chứa phân phối hậu nghiệm cho mỗi tham số (terms, parameters) trong mô hình, và kết quả cũng được trình bày theo cách khác. Tuy nhiên khi áp dụng hàm tidy, kết quả mô hình Bayes cũng được tóm tắt gọn lại theo cùng một cách thức:

Kết quả này có thể được diễn giải tương tự như bất cứ mô hình nào, chỉ khác một chút về thuật ngữ: khoảng tin cậy trong thống kê Bayes được gọi là Highest posterior density interval(HPDI hay HDI).

brms_sum=tidy(fit,
     robust=TRUE, 
     conf.method="HPDinterval", 
     conf.level = 0.975)

brms_sum
## # A tibble: 7 x 8
##   effect  component group  term      estimate std.error conf.low conf.high
##   <chr>   <chr>     <chr>  <chr>        <dbl>     <dbl>    <dbl>     <dbl>
## 1 fixed   cond      <NA>   (Interce~   34.2       3.37   2.45e+1    42.1  
## 2 fixed   cond      <NA>   wt          -4.25      1.11  -7.15e+0    -1.37 
## 3 ran_pa~ cond      cyl    sd__(Int~    4.38      2.98   4.37e-1    12.2  
## 4 ran_pa~ cond      gear   sd__(Int~    4.13      2.90   1.96e-1    13.4  
## 5 ran_pa~ cond      gear   sd__wt       1.29      1.07   3.43e-3     7.18 
## 6 ran_pa~ cond      gear   cor__(In~   -0.595     0.435 -9.97e-1     0.895
## 7 ran_pa~ cond      Resid~ sd__Obse~    2.47      0.387  1.73e+0     3.47

Lợi thế của dữ liệu dạng bảng này đó là ta có thể đưa nó vào pipe để vẽ một biểu đồ tương tự như hình bên dưới:

brms_sum%>%ggplot(aes(estimate,term,xmin=conf.low,xmax=conf.high,col=estimate))+
  geom_errorbarh(height=0)+
  geom_vline(xintercept=0,lty=2,col="black")+
  geom_point(size=3)+
  scale_color_gradientn(colours = pals::coolwarm(10))+
  theme_bw()

Mô hình mixed model Bayes cho phép ước lượng được cả phân phối hậu nghiệm đến mức độ cá thể:

brms_ran_val=tidy(fit, effects = "ran_vals", 
     robust = T,
     conf.method="HPDinterval", 
     conf.level = 0.975) 

brms_ran_val
## # A tibble: 9 x 9
##   effect component group level term  estimate std.error conf.low conf.high
##   <chr>  <chr>     <chr> <chr> <chr>    <dbl>     <dbl>    <dbl>     <dbl>
## 1 ran_v~ cond      cyl   4     (Int~    2.15      2.43     -2.80      8.35
## 2 ran_v~ cond      cyl   6     (Int~   -0.963     1.95     -5.54      4.39
## 3 ran_v~ cond      cyl   8     (Int~   -1.90      2.39     -7.45      1.40
## 4 ran_v~ cond      gear  3     (Int~   -2.55      4.31    -15.2       3.34
## 5 ran_v~ cond      gear  4     (Int~    0.846     2.28     -4.88      7.16
## 6 ran_v~ cond      gear  5     (Int~   -0.110     2.10     -7.89      4.40
## 7 ran_v~ cond      gear  3     wt       0.565     1.01     -1.21      4.61
## 8 ran_v~ cond      gear  4     wt      -0.137     0.733    -2.58      3.06
## 9 ran_v~ cond      gear  5     wt      -0.243     0.661    -2.55      3.15
brms_ran_val%>%
  ggplot(aes(estimate,level,xmin=conf.low,xmax=conf.high,col=group))+
  geom_errorbarh(height=0)+
  geom_vline(xintercept=0,lty=2,col="black")+
    facet_grid(term~group,scale="free_x")+
  geom_point(size=3)+
  theme_bw()

Bây giờ ta thử một mô hình Bayes mixed model khác với package rstanarm:

library(rstanarm)

stan_fit <- stan_glmer(mpg ~ wt + (1|cyl) + (1+wt|gear), data = mtcars,
                  iter = 300, chains = 2)
## 
## SAMPLING FOR MODEL 'continuous' NOW (CHAIN 1).
## 
## Gradient evaluation took 0 seconds
## 1000 transitions using 10 leapfrog steps per transition would take 0 seconds.
## Adjust your expectations accordingly!
## 
## 
## Iteration:   1 / 300 [  0%]  (Warmup)
## Iteration:  30 / 300 [ 10%]  (Warmup)
## Iteration:  60 / 300 [ 20%]  (Warmup)
## Iteration:  90 / 300 [ 30%]  (Warmup)
## Iteration: 120 / 300 [ 40%]  (Warmup)
## Iteration: 150 / 300 [ 50%]  (Warmup)
## Iteration: 151 / 300 [ 50%]  (Sampling)
## Iteration: 180 / 300 [ 60%]  (Sampling)
## Iteration: 210 / 300 [ 70%]  (Sampling)
## Iteration: 240 / 300 [ 80%]  (Sampling)
## Iteration: 270 / 300 [ 90%]  (Sampling)
## Iteration: 300 / 300 [100%]  (Sampling)
## 
##  Elapsed Time: 0.357 seconds (Warm-up)
##                0.109 seconds (Sampling)
##                0.466 seconds (Total)
## 
## 
## SAMPLING FOR MODEL 'continuous' NOW (CHAIN 2).
## 
## Gradient evaluation took 0 seconds
## 1000 transitions using 10 leapfrog steps per transition would take 0 seconds.
## Adjust your expectations accordingly!
## 
## 
## Iteration:   1 / 300 [  0%]  (Warmup)
## Iteration:  30 / 300 [ 10%]  (Warmup)
## Iteration:  60 / 300 [ 20%]  (Warmup)
## Iteration:  90 / 300 [ 30%]  (Warmup)
## Iteration: 120 / 300 [ 40%]  (Warmup)
## Iteration: 150 / 300 [ 50%]  (Warmup)
## Iteration: 151 / 300 [ 50%]  (Sampling)
## Iteration: 180 / 300 [ 60%]  (Sampling)
## Iteration: 210 / 300 [ 70%]  (Sampling)
## Iteration: 240 / 300 [ 80%]  (Sampling)
## Iteration: 270 / 300 [ 90%]  (Sampling)
## Iteration: 300 / 300 [100%]  (Sampling)
## 
##  Elapsed Time: 0.39 seconds (Warm-up)
##                0.263 seconds (Sampling)
##                0.653 seconds (Total)
summary(stan_fit)
## 
## Model Info:
## 
##  function:     stan_glmer
##  family:       gaussian [identity]
##  formula:      mpg ~ wt + (1 | cyl) + (1 + wt | gear)
##  algorithm:    sampling
##  priors:       see help('prior_summary')
##  sample:       300 (posterior sample size)
##  observations: 32
##  groups:       cyl (3), gear (3)
## 
## Estimates:
##                                       mean   sd     2.5%   25%    50% 
## (Intercept)                           32.8    3.5   25.7   30.7   32.9
## wt                                    -4.0    1.3   -7.2   -4.6   -3.8
## b[(Intercept) cyl:4]                   2.6    2.3   -1.6    1.1    2.4
## b[(Intercept) cyl:6]                  -0.5    2.1   -4.9   -1.7   -0.3
## b[(Intercept) cyl:8]                  -1.7    2.3   -7.2   -2.8   -1.5
## b[(Intercept) gear:3]                 -1.1    2.4   -7.6   -0.9   -0.1
## b[wt gear:3]                           0.4    1.1   -1.2    0.0    0.1
## b[(Intercept) gear:4]                  0.6    1.5   -1.1   -0.1    0.1
## b[wt gear:4]                           0.0    0.9   -2.1   -0.2    0.0
## b[(Intercept) gear:5]                  0.2    1.6   -1.9   -0.2    0.0
## b[wt gear:5]                          -0.3    1.0   -2.6   -0.6   -0.1
## sigma                                  2.6    0.3    2.1    2.4    2.6
## Sigma[cyl:(Intercept),(Intercept)]    12.1   18.8    0.1    2.7    5.8
## Sigma[gear:(Intercept),(Intercept)]    3.2    7.4    0.0    0.1    0.3
## Sigma[gear:wt,(Intercept)]            -0.4    1.6   -5.0   -0.5    0.0
## Sigma[gear:wt,wt]                      1.5    3.9    0.0    0.0    0.2
## mean_PPD                              20.1    0.7   18.9   19.7   20.1
## log-posterior                       -108.4    3.5 -115.3 -110.7 -108.1
##                                       75%    97.5%
## (Intercept)                           34.7   39.7 
## wt                                    -3.3   -1.8 
## b[(Intercept) cyl:4]                   3.9    7.4 
## b[(Intercept) cyl:6]                   0.8    3.8 
## b[(Intercept) cyl:8]                  -0.4    2.1 
## b[(Intercept) gear:3]                  0.1    1.2 
## b[wt gear:3]                           0.6    3.4 
## b[(Intercept) gear:4]                  0.9    4.8 
## b[wt gear:4]                           0.2    1.4 
## b[(Intercept) gear:5]                  0.4    3.6 
## b[wt gear:5]                           0.0    1.5 
## sigma                                  2.8    3.3 
## Sigma[cyl:(Intercept),(Intercept)]    13.3   60.0 
## Sigma[gear:(Intercept),(Intercept)]    2.5   24.0 
## Sigma[gear:wt,(Intercept)]             0.0    1.2 
## Sigma[gear:wt,wt]                      1.5    9.2 
## mean_PPD                              20.7   21.4 
## log-posterior                       -105.8 -103.0 
## 
## Diagnostics:
##                                     mcse Rhat n_eff
## (Intercept)                         0.3  1.0  162  
## wt                                  0.2  1.0   36  
## b[(Intercept) cyl:4]                0.2  1.0   86  
## b[(Intercept) cyl:6]                0.2  1.0   89  
## b[(Intercept) cyl:8]                0.2  1.0  103  
## b[(Intercept) gear:3]               0.4  1.1   30  
## b[wt gear:3]                        0.2  1.1   28  
## b[(Intercept) gear:4]               0.2  1.0   62  
## b[wt gear:4]                        0.1  1.0   67  
## b[(Intercept) gear:5]               0.2  1.0   95  
## b[wt gear:5]                        0.1  1.0   62  
## sigma                               0.0  1.0  300  
## Sigma[cyl:(Intercept),(Intercept)]  2.2  1.0   72  
## Sigma[gear:(Intercept),(Intercept)] 1.3  1.0   33  
## Sigma[gear:wt,(Intercept)]          0.1  1.0  166  
## Sigma[gear:wt,wt]                   0.5  1.0   57  
## mean_PPD                            0.0  1.0  238  
## log-posterior                       0.3  1.0  129  
## 
## For each parameter, mcse is Monte Carlo standard error, n_eff is a crude measure of effective sample size, and Rhat is the potential scale reduction factor on split chains (at convergence Rhat=1).

Nội dung quá dài dòng này sẽ được tóm gọn bằng hàm tidy, theo cùng một cách như ta đã biết

tidy(stan_fit, conf.int = TRUE, conf.method = "HPDinterval", prob = 0.95)
## # A tibble: 2 x 5
##   term        estimate std.error conf.low conf.high
##   <chr>          <dbl>     <dbl>    <dbl>     <dbl>
## 1 (Intercept)    32.9      3.00     26.6      38.1 
## 2 wt             -3.85     0.984    -6.10     -2.35
tidy(stan_fit, effects = "ran_pars")
## # A tibble: 5 x 3
##   term                    group    estimate
##   <chr>                   <chr>       <dbl>
## 1 sd_(Intercept).cyl      cyl         3.47 
## 2 sd_(Intercept).gear     gear        1.79 
## 3 sd_wt.gear              gear        1.23 
## 4 cor_(Intercept).wt.gear gear       -0.193
## 5 sd_Observation.Residual Residual    2.63
stan_fit%>%tidy(effects = "ran_vals",
                conf.int = TRUE, 
                conf.method = "HPDinterval")%>%
  group_by(group,level) %>%
  mutate(keep=any(estimate > 0))%>%
  ggplot(aes(estimate,level,
             xmin=conf.low,xmax=conf.high,
             col=factor(keep)))+
  geom_errorbarh(height=0)+
  geom_vline(xintercept=0,lty=2)+
  geom_point(size=3)+
    facet_grid(term~group,scale="free_x")+
  scale_colour_manual(values=c("blue","red"),guide=FALSE)+
  theme_bw()

Cuối cùng, ta thử dùng hàm tidy cho mô hình dựng bằng gamlss. Như đã nói, gamlss là một package khá kì dị, kết quả mà nó xuất ra không giống với bất cứ gói nào khác, và rất khó khai thác.Trong thí dụ này, ta dựng một mô hình BoxCox t với dataset abdomen.Ghi chú: đây không phải là mixed model.

data(abdom, package="gamlss.data") 

library(gamlss)
## Loading required package: splines
## Loading required package: gamlss.data
## Loading required package: gamlss.dist
## Loading required package: MASS
## 
## Attaching package: 'MASS'
## The following object is masked from 'package:dplyr':
## 
##     select
## Loading required package: nlme
## 
## Attaching package: 'nlme'
## The following object is masked from 'package:dplyr':
## 
##     collapse
## Loading required package: parallel
##  **********   GAMLSS Version 5.1-0  **********
## For more on GAMLSS look at http://www.gamlss.org/
## Type gamlssNews() to see new features/changes/bug fixes.
gamlss_mod<- gamlss(y~pb(x),sigma.fo=~pb(x),family=BCT,data=abdom)
## GAMLSS-RS iteration 1: Global Deviance = 4771.925 
## GAMLSS-RS iteration 2: Global Deviance = 4771.039 
## GAMLSS-RS iteration 3: Global Deviance = 4770.999 
## GAMLSS-RS iteration 4: Global Deviance = 4770.994 
## GAMLSS-RS iteration 5: Global Deviance = 4770.993
summary(gamlss_mod)
## ******************************************************************
## Family:  c("BCT", "Box-Cox t") 
## 
## Call:  gamlss(formula = y ~ pb(x), sigma.formula = ~pb(x),  
##     family = BCT, data = abdom) 
## 
## Fitting method: RS() 
## 
## ------------------------------------------------------------------
## Mu link function:  identity
## Mu Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -72.19470    1.33349  -54.14   <2e-16 ***
## pb(x)        11.05556    0.05785  191.12   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ------------------------------------------------------------------
## Sigma link function:  log
## Sigma Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -2.65086    0.10861 -24.406  < 2e-16 ***
## pb(x)       -0.01004    0.00380  -2.641  0.00849 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ------------------------------------------------------------------
## Nu link function:  identity 
## Nu Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept)  -0.1076     0.6301  -0.171    0.865
## 
## ------------------------------------------------------------------
## Tau link function:  log 
## Tau Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   2.4903     0.4247   5.864 7.47e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ------------------------------------------------------------------
## NOTE: Additive smoothing terms exist in the formulas: 
##  i) Std. Error for smoothers are for the linear effect only. 
## ii) Std. Error for the linear terms maybe are not accurate. 
## ------------------------------------------------------------------
## No. of observations in the fit:  610 
## Degrees of Freedom for the fit:  11.76162
##       Residual Deg. of Freedom:  598.2384 
##                       at cycle:  5 
##  
## Global Deviance:     4770.993 
##             AIC:     4794.516 
##             SBC:     4846.426 
## ******************************************************************
tidy(gamlss_mod)
##   parameter        term     estimate   std.error   statistic       p.value
## 1        mu (Intercept) -72.19470143 1.327904569 -54.3673869 7.314947e-235
## 2        mu       pb(x)  11.05556002 0.057722851 191.5283099  0.000000e+00
## 3     sigma (Intercept)  -2.65086130 0.108135709 -24.5142083  9.885987e-93
## 4     sigma       pb(x)  -0.01003582 0.003788057  -2.6493332  8.275390e-03
## 5        nu (Intercept)  -0.10755887 0.557700059  -0.1928615  8.471317e-01
## 6       tau (Intercept)   2.49027048 0.299096364   8.3259804  5.526415e-16

4 Nhận xét

Package broom.mixed là một công cụ rất hữu ích, nó giúp giải quyết được các vấn đề trở ngại khi thực hành mixed model bao gồm:

  1. Sử dụng cùng một danh pháp thuật ngữ cho những thành phần trong nội dung mô hình, dù mô hình được dựng bằng package nào. Các terms được phân chia theo thứ bậc: effect (fixed hay random), nếu là random thì là parameters hay values ? kế đến là terms (intercept, các factors, predictors trong mô hình, sd hay cor_intercept), giúp dễ dàng phân biệt ý nghĩa của các thành phần này.

  2. Giản lược những chi tiết thừa thải trong nội dung mô hình, chỉ giữ lại những thông tin thiết yếu nhất để suy diễn thống kê

  3. Tách riêng 3 phần thông tin về phẩm chất mô hình, nội dung mô hình và giá trị dự báo

  4. Quy đồng tất cả kết quả từ 8-9 package khác nhau về cùng một bảng kết quả có cấu trúc nhất quán.

  5. Kết quả được xuất ra dưới dạng tibble, cho phép thực hiện các hàm phân tích sâu, sử dụng pipeline và vẽ biểu đồ bằng ggplot.

Bài thực hành đến đây là hết. Tạm biệt các bạn và hẹn gặp lại…

LS0tDQp0aXRsZTogIkdp4bubaSB0aGnhu4d1IHBhY2thZ2UgYnJvb20ubWl4ZWQiDQphdXRob3I6ICJMw6ogTmfhu41jIEto4bqjIE5oaSINCmRhdGU6ICIyMyBUaMOhbmcgMTAgMjAxOCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6ICJkZWZhdWx0Ig0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIVtdKGJyb29tbWl4ZWQucG5nKQ0KDQojIEdp4bubaSB0aGnhu4d1DQoNCk1peGVkIG1vZGVsIGzDoCBt4buZdCBk4bqhbmcgbcO0IGjDrG5oIGjhu5NpIHF1eSBjaG8gcGjDqXAga2jhuqNvIHPDoXQgxJHhu5NuZyB0aOG7nWkgaGnhu4d1IOG7qW5nIGNow61uaCAoZml4ZWQgZWZmZWN0KSB2w6AgaGnhu4d1IOG7qW5nIG5n4bqrdSBuaGnDqm4gKHJhbmRvbSBlZmZlY3QpIHbhu5tpIG5oaeG7gXUg4bupbmcgZOG7pW5nIHRyb25nIG5naGnDqm4gY+G7qXUgeSBo4buNYy4gUGjGsMahbmcgcGjDoXAgbsOgeSDEkcaw4bujYyBo4buXIHRy4bujIHLhuqV0IHPDonUgdsOgIHLhu5luZyB0cm9uZyBuZ8O0biBuZ+G7ryBSLMSRw6J5IGzDoCBt4buZdCBs4bujaSB0aOG6vy4gVHV5IG5oacOqbiwgxJHDonkgY8WpbmcgY2jDrW5oIGzDoCB0cuG7nyBuZ+G6oWkgcXVhbiB0cuG7jW5nIC0gdsOsIGPDsyBxdcOhIG5oaeG7gXUgUiBwYWNrYWdlcyDEkeG7gyBk4buxbmcgbWl4ZWQgbW9kZWwsIG3hu5dpIGxv4bqhaSBkw7luZyBjw7ogcGjDoXAga2jDoWMgbmhhdSwgdsOgIGNobyByYSBr4bq/dCBxdeG6oyB24bubaSBu4buZaSBkdW5nIHbDoCBj4bqldSB0csO6YyBraMOhYyBuaGF1ICh24buBIGNsYXNzLCB24buBIGPDoWMgdGjDoG5oIHBo4bqnbiB0aGFtIHPhu5EsIGjDrG5oIHRo4bupYyB0csOsbmggYsOgeSwgc3V5IGRp4buFbiB0aOG7kW5nIGvDqi4uLiksIMSRYSBwaOG6p24gcuG7kWkgcuG6r20ga2jDsyBkaeG7hW4gZ2nhuqNpIHbDoCBi4bqvdCBideG7mWMgZMO5bmcgbmjhu69uZyBwaMawxqFuZyBwaMOhcCBo4bqtdSBraeG7g20sIHBow6JuIHTDrWNoIGNodXnDqm4gYmnhu4d0IHbDoCB0xrDGoW5nIHRow61jaCB24bubaSBt4buXaSBwYWNrYWdlLiANCg0KTmfDoHkgMjEvMTAgduG7q2EgcXVhLCBwYWNrYWdlIGJyb29tLm1peGVkIMSRxrDhu6NjIGPDtG5nIGLhu5EgdHLDqm4gQ1JBTiwgdsOgIE5oaSBz4bq9IGdp4bubaSB0aGnhu4d1IHbhu5tpIGPDoWMgYuG6oW4gduG7gSBuw7MgdHJvbmcgYsOgaSBuw6B5LiDEkMOieSBsw6AgbeG7mXQgY8O0bmcgY+G7pSB0aeG7h24gbOG7o2ksIGNobyBwaMOpcCBjaMO6bmcgdGEgZGnhu4VuIGdp4bqjaSB2w6Aga2hhaSB0aMOhYyBu4buZaSBkdW5nIGPhu6dhIG5o4buvbmcgbcO0IGjDrG5oIE1peGVkIG1vZGVsIGThu7FuZyBi4bqxbmcgYuG6pXQgY+G7qSBwYWNrYWdlcyBuw6BvIHRoZW8gY8O5bmcgbeG7mXQgY8OhY2ggdGjhu6ljLiBOaMawIHbhuq15LCBjw7RuZyBk4bulbmcgY+G7p2EgYnJvb20ubWl4ZWQgdMawxqFuZyB04buxIG5oxrAgcGFja2FnZSBicm9vbSBtw6AgTmhpIHThu6tuZyBnaeG7m2kgdGhp4buHdSwgbmjGsG5nIGNodXnDqm4gYmnhu4d0IGNobyBtaXhlZCBtb2RlbC4NCg0KTmjhu69uZyB0w61uaCBuxINuZyB0aeG7h24gZOG7pW5nIGPhu6dhIGJyb29tLm1peGVkIHPhur0gbOG6p24gbMaw4bujdCDEkcaw4bujYyBtaW5oIGjhu41hIGTGsOG7m2kgxJHDonkNCg0KIyBQaOG6oW0gdmkg4bupbmcgZOG7pW5nDQoNClBhY2thZ2UgYnJvb20ubWl4ZWQgdMawxqFuZyB0aMOtY2ggduG7m2kgdOG6pXQgY+G6oyBuaOG7r25nIMSR4buLbmggZOG6oW5nIG1peGVkIG1vZGVsIHThu6tuZyB4deG6pXQgaGnhu4duIHRyb25nIG5nw7RuIG5n4buvIFIsIGJhbyBn4buTbSBwaMOhaSBmcmVxdWVudGlzdCB24bubaSBo4buNIG3DtCBow6xuaCBsbWVyL2dsbWVyIChwYWNrYWdlIGxtZTQpLCBsbWUvZ2xzL25sbWUgKHBhY2thZ2UgbmxtZSksIGdsbW1UTUIgIGdsbW1hZG1iIChwYWNrYWdlIGdsbW1UTUIsIGdsbW1BRE1CKSwgdsOgIHBow6FpIEJheWVzOiBicm1zZml0IChwYWNrYWdlIGJybXMpLCBzdGFucmVnIChwYWNrYWdlIHJzdGFuYXJtKSwgTUNNQ2dsbW0sIHRo4bqtbSBjaMOtIG7DsyDEkeG7jWMgxJHGsOG7o2MgY+G6oyBsb+G6oWkgbcO0IGjDrG5oIEdBTUxTUyAocGFja2FnZSBnYW1sc3MpIHbhu5FuIGzDoCBraeG7g3UgxJHhu4tuaCBk4bqhbmcgbWl4ZWQgbW9kZWwga2jDsyBjaOG7i3UgbmjhuqV0Lg0KDQpDw7RuZyBk4bulbmcgY+G7p2EgcGFja2FnZSBicm9vbS5taXhlZCBsacOqbiBxdWFuIMSR4bq/biAzIHBoxrDGoW5nIHBow6FwIChmdW5jdGlvbikgOg0KDQoxKSBnbGFuY2U6IHTDs20gdOG6r3QgcGjhuqltIGNo4bqldCBj4bunYSBtaXhlZCBtb2RlbCwgbsOzIGN1bmcgY+G6pXAgdGjDtG5nIHRpbiBraMOhaSBxdcOhdCB24buBIHPhu7EgcGjDuSBo4bujcCBj4bunYSBtb2RlbCB24bubaSBk4buvIGxp4buHdSwgduG7m2kgbmjhu69uZyB0acOqdSBjaMOtIG5oxrAgQUlDLCBMT09JQywgUjIuDQoNCjIpIGF1Z21lbnQ6IHBow6JuIHTDrWNoIHPDonUgduG7gSB0w61uaCBjaMOtbmggeMOhYyBj4bunYSBtw7QgaMOsbmgsIGPhu6UgdGjhu4MgbMOgIHNhaSBz4buRIHThu5NuIGzGsHUgKHJlc2lkdWFsIGVycm9yKSwgZ2nDoSB0cuG7iyBk4buxIGLDoW8gKHByZWRpY3RlZCB2YWx1ZXMpIHRyw6puIGNow61uaCBk4buvIGxp4buHdSBoaeG7h24gaMOgbmggaGF5IGThu68gbGnhu4d1IGfhu5FjLCB2w6AgY8OhYyBjaOG7iSBz4buRIGtow6FjIChpbmZsdWVuY2UuLi4pLkThu68gbGnhu4d1IHh14bqldCByYSBjw7JuIGPDsyB0aOG7gyBkw7luZyDEkeG7gyB0csOsbmggYsOgeSBiaeG7g3UgxJHhu5MgxJHhu4Mgc28gc8OhbmggZ2nDoSB0cuG7iyBk4buxIGLDoW8gdsOgIHF1YW4gc8OhdCB0aOG7sWMgdOG6vy4NCg0KMykgQ3Xhu5FpIGPDuW5nLCBwaMawxqFuZyBwaMOhcCB0aWR5OyBjaG8gcGjDqXAgdHLDrG5oIGLDoHkgbuG7mWkgZHVuZyBtw7QgaMOsbmggbmjhurFtIHBo4bulYyB24bulIGNobyBzdXkgZGnhu4VuIHRo4buRbmcga8OqIChlc3RpbWF0ZSwgcy5lLCBDSSAoY2hvIGZyZXF1ZW50aXN0KSBoYXkgSERJIChwaMOhaSBCYXllcyksIHBfdmFsdWVzIFdhbGQgdGVzdC4uLiksIMSR4bq3YyBiaeG7h3QgbmfGsOG7nWkgZMO5bmcgY8OzIHRo4buDIHTDuXkgY2jhu4luaCBuZ8aw4buhbmcgdHLDqm4vZMaw4bubaSBj4bunYSBDSSwgdMOtbmggQ0kgYuG6sW5nIGJvb3RzdHJhcCxjaOG7jW4gTWVhbiBoYXkgbWVkaWFuIGNobyBlc3RpbWF0ZS4uLiBOaOG7r25nIHRow7RuZyBz4buRIG7DoHkgxJHGsOG7o2MgY2h1eeG7g24gdGjDoG5oIHRpYmJsZSwgbsOqbiBjw7MgdGjhu4MgxJHGsOG7o2MgZMO5bmcgxJHhu4MgduG6vSBiaeG7g3UgxJHhu5MgZOG7hSBkw6BuZyBi4bqxbmcgZ2dwbG90Mg0KDQpgYGB7ciByZXN1bHRzPSJhc2lzIixlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KY2MgPC0gcmVhZC5jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9iYm9sa2VyL2Jyb29tLm1peGVkL21hc3Rlci9pbnN0L2NhcGFiaWxpdGllcy5jc3YiKQ0KaWYgKHJlcXVpcmUoInBhbmRlciIpKSB7DQogICAgcGFuZGVyOjpwYW5kZXIoY2Msc3BsaXQudGFibGVzPUluZikNCn0NCmBgYA0KDQojIFRow60gZOG7pSBtaW5oIGjhu41hDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGJyb29tLm1peGVkKQ0KYGBgDQoNCsSQ4bqndSB0acOqbiwgTmhpIHPhur0gdGjhu60gYnJvb20ubWl4ZWQgdHLDqm4gMiBwYWNrYWdlIHRow7RuZyBk4bulbmcgbmjhuqV0IGNobyBtaXhlZCBtb2RlbCwgxJHDsyBsw6AgbmxtZSB2w6AgbG1lNC4gVuG7m2kgcGFja2FnZSBubG1lLCB0YSB0aOG7rSBk4buxbmcgbeG7mXQgbcO0IGjDrG5oIG1peGVkIG1vZGVsIHbhu5tpIHJhbmRvbSBzbG9wZSBjaG8gZGF0YXNldCAic2xlZXAgc3R1ZHkiIHF1ZW4gdGh14buZYzsNCg0KTmjGsCBjw6FjIGLhuqFuIHRo4bqleTogbuG7mWkgZHVuZyBuZ3V5w6puIHRo4buneSBj4bunYSBtw7QgaMOsbmggbmxtZSBiYW8gZ+G7k20gMyBwaOG6p247IGhp4buHdSDhu6luZyByYW5kb20gxJHGsOG7o2Mga2jhuqNvIHPDoXQgZMaw4bubaSBk4bqhbmcgd2l0aGluIGdyb3VwIHJlc2lkdWFscywgY8OybiBr4bq/dCBxdeG6oyBoaeG7h3Ug4bupbmcgY2jDrW5oIGfhuqduIGdp4buRbmcgbmjGsCBtw7QgaMOsbmggZ2xtLiBOZ2/DoGkgcmEgY8OybiBjw7MgY8OhYyB0acOqdSBjaMOtIHBo4bqpbSBjaOG6pXQgbcO0IGjDrG5oIG5oxrAgQUlDLCBCSUMgdsOgIExvZ0xpa2VsaWhvb2QuIEvhur90IHF14bqjIG7DoHkgY8OzIHF1w6Egbmhp4buBdSBjaGkgdGnhur90IHRo4burYSB0aOG6o2ksIGtow7RuZyBjw7Mgw61jaCB2w6AgdGjhuq1tIGNow60ga2jDsyBoaeG7g3UgxJHhu5FpIHbhu5tpIG5oaeG7gXUgYuG6oW4uDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZGF0YSgic2xlZXBzdHVkeSIsIHBhY2thZ2U9ImxtZTQiKQ0KDQpubG1lX21vZGVsIDwtIG5sbWU6OmxtZShSZWFjdGlvbiB+IERheXMsIHJhbmRvbSA9fiBEYXlzfFN1YmplY3QsIGRhdGE9c2xlZXBzdHVkeSkNCg0Kc3VtbWFyeShubG1lX21vZGVsKQ0KYGBgDQoNClbhu5tpIGjDoG0gdGlkeSwgdGEgY8OzIHRo4buDIHTDs20gdOG6r3Qgbmjhu69uZyBu4buZaSBkdW5nIGNow61uaCBj4bunYSBtw7QgaMOsbmggbmjGsCBzYXU6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdGlkeShubG1lX21vZGVsKQ0KYGBgDQoNCkvhur90IHF14bqjIHRyw6xuaCBiw6B5IHRyb25nIDEgYuG6o25nIGfhu41uIGfDoG5nOiBjw6FjIHRoYW0gc+G7kSBj4bunYSBtw7QgaMOsbmggxJHGsOG7o2MgY2hpYSB0aMOgbmggbmjDs206IGVmZmVjdCBsw6AgbmjDs20gaGnhu4d1IOG7qW5nIHbhu5tpIEZpeGVkID0gaGnhu4d1IOG7qW5nIGNow61uaCwgcmFuX3BhcnMgbMOgIHRoYW0gc+G7kSBjaG8gaGnhu4d1IOG7qW5nIG5n4bqrdSBuaGnDqm4sIGPDoWMgaGnhu4d1IOG7qW5nIG7DoHkgbOG6oWkgxJHGsOG7o2MgcGjDom4gYuG7kSBjaG8gcXXhuqduIHRo4buDIChmaXhlZCksIFN1YmplY3QgKGPDoSB0aOG7gykgdsOgIHJlc2lkdWFsOiBzYWkgc+G7kSBraMO0bmcgZ2nhuqNpIHRow61jaCDEkcaw4bujYywgdMawxqFuZyDhu6luZyB24bubaSBjw6FjIHRlcm1zIHRyb25nIG3DtCBow6xuaCBiYW8gZ+G7k20gSW50ZXJjZXB0LCBiaeG6v24gxJHhu5ljIGzhuq1wLCBzZCBj4bunYSByYW5kb20gZWZmZWN0cywgY29ycmVsYXRpb24gdsOgIHJzZCB0aGVvIGPDoSB0aOG7gy4gVuG7m2kgZml4ZWQgZWZmZWN0LCBr4bq/dCBxdeG6oyBn4buTbSBNZWFuLCBTRSwgZGYsIGtp4buDbSDEkeG7i25oIFdhbGQgdsOgIHAudmFsdWVzLg0KDQpIw6xuaCB0aOG7qWMgdHLDrG5oIGLDoHkgbsOgeSBk4buFIGhp4buDdSwgdHJvbmcgc8OhbmcgaMahbiBuaGnhu4F1IHNvIHbhu5tpIG7hu5lpIGR1bmcgZ+G7kWMuDQoNCkLhuqFuIGPDsyB0aOG7gyB0aMOqbSBi4bubdCBhcmd1bWVudHMgxJHhu4MgbOG7sWEgY2jhu41uOiBDaOG7iSB4deG6pXQga+G6v3QgcXXhuqMgY2hvIGhp4buHdSDhu6luZyBjaMOtbmgsIHbDoCB0aMOqbSBraG/huqNuZyB0aW4gY+G6rXkgOTAlIChoYXkgbeG7mXQgbmfGsOG7oW5nIGNhbyBoxqFuLCBu4bq/dSBi4bqhbiB0aMOtY2gpDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdGlkeShubG1lX21vZGVsLCBlZmZlY3RzID0gImZpeGVkIiwgY29uZi5pbnQgPSBUKQ0KYGBgDQoNCktow7RuZyBjaOG7iSBraOG6o28gc8OhdCBoaeG7h3Ug4bupbmcgbmfhuqt1IG5oacOqbiBkxrDhu5tpIGjDrG5oIHRo4bupYyBwYXJhbWV0ZXJzLCBow6BtIHRpZHkgY8OybiBjaG8gcGjDqXAgxrDhu5tjIGzGsOG7o25nIGdpw6EgdHLhu4sgcmFuZG9tIGVmZmVjdCBjaG8gdOG7q25nIGPDoSB0aOG7gzoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQp0aWR5KG5sbWVfbW9kZWwsIA0KICAgICBlZmZlY3RzID0gInJhbl92YWxzIiklPiVoZWFkKCkNCmBgYA0KDQpIw6BtIGdsYW5jZSBz4bq9IHh14bqldCByacOqbmcgZ2nDoSB0cuG7iyBMb2dMaWtlbGlob29kLCBBSUMsIEJJQyB2w6Agc2lnbWENCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpnbGFuY2UobmxtZV9tb2RlbCkNCmBgYA0KDQpIw6BtIGF1Z21lbnQgY2hvIHBow6lwIHh14bqldCBnacOhIHRy4buLIGThu7EgYsOhbyBjaG8gY2jDrW5oIGThu68gbGnhu4d1IGfhu5FjLCBob+G6t2Mgw6FwIGThu6VuZyBjaG8gbeG7mXQgZGF0YXNldCBt4bubaXbDoCB4deG6pXQga+G6v3QgcXXhuqMgZMaw4bubaSBk4bqhbmcgZGF0YWZyYW1lOiB0xrDGoW5nIHThu7EgbmjGsCBow6BtIHByZWRpY3QNCg0KYGBge3J9DQphdWdtZW50KG5sbWVfbW9kZWwsIG5ld2RhdGE9c2FtcGxlX2ZyYWMoc2xlZXBzdHVkeSwwLjIpKSU+JWhlYWQoKQ0KYGBgDQoNCkThu68gbGnhu4d1IG7DoHkgY2hvIHBow6lwIHbhur0gbmjhu69uZyBiaeG7g3UgxJHhu5MgbmjGsCBzYXUgxJHhu4Mga2nhu4NtIHRyYSB0cuG7sWMgcXVhbiBob+G6oXQgxJHhu5luZyBj4bunYSBtw7QgaMOsbmg6DQoNCmBgYHtyfQ0KYXVnbWVudChubG1lX21vZGVsLCBuZXdkYXRhPXNhbXBsZV9mcmFjKHNsZWVwc3R1ZHksMC4yKSklPiUNCiAgZ2F0aGVyKFJlYWN0aW9uLC5maXR0ZWQsa2V5PSJUeXBlIix2YWx1ZT0iU2NvcmUiKSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9kZW5zaXR5KGFlcyh4PVNjb3JlLGZpbGw9VHlwZSksYWxwaGE9MC41KSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCg0KYGBge3J9DQphdWdtZW50KG5sbWVfbW9kZWwsIG5ld2RhdGE9c2FtcGxlX2ZyYWMoc2xlZXBzdHVkeSwwLjIpKSU+JQ0KICBnYXRoZXIoUmVhY3Rpb24sLmZpdHRlZCxrZXk9IlR5cGUiLHZhbHVlPSJTY29yZSIpJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX3Ntb290aChhZXMoeD1EYXlzLHk9U2NvcmUsZmlsbD1UeXBlLGNvbD1UeXBlKSxhbHBoYT0wLjIsbWV0aG9kPSJnbG0iKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCkLDonkgZ2nhu50gY2jDum5nIHRhIHF1YSBwYWNrYWdlIGxtZTQsIHbDoCB0aOG7rSBk4buxbmcgbeG7mXQgbG9naXN0aWMgbWl4ZWQgbW9kZWwgY2hvIGRhdGFzZXQgImNicHAiDQoNCk5oxrAgdGEgdGjhuqV5LCBu4buZaSBkdW5nIG5ndXnDqm4gdGjhu6d5IGPhu6dhIG3DtCBow6xuaCBsbWU0IMSRxrDhu6NjIHRyw6xuaCBiw6B5IGhvw6BuIHRvw6BuIGtow6FjIHNvIHbhu5tpIG3DtCBow6xuaCBubG1lLCBjw6FjIHRoYW0gc+G7kSBmaXhlZCBlZmZlY3QgxJHGsOG7o2Mga2jhuqNvIHPDoXQgYuG6sW5nIFNFIHbDoCB0ZXN0IFosIHRyb25nIGtoaSByYW5kb20gZWZmZWN0IMSRxrDhu6NjIHRyw6xuaCBiw6B5IGTGsOG7m2kgZOG6oW5nIHZhcmlhbmNlIHbDoCBTRCBj4bunYSBpbnRlcmNlcHQuIE5nb8OgaSByYSBjw7JuIGPDsyB0aMOqbSBt4buZdCBjb3JyZWxhdGlvbiBtYXRyaXggY2hvIGZpeGVkIGVmZmVjdHMNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkYXRhKCJjYnBwIiwgcGFja2FnZT0ibG1lNCIpDQoNCmxtZTRfbG9naXN0aWMgPC0gbG1lNDo6Z2xtZXIoY2JpbmQoaW5jaWRlbmNlLCBzaXplIC0gaW5jaWRlbmNlKSB+IHBlcmlvZCArICgxIHwgaGVyZCksDQogICAgICAgICAgICAgICBkYXRhID0gY2JwcCwgZmFtaWx5ID0gYmlub21pYWwpDQoNCnN1bW1hcnkobG1lNF9sb2dpc3RpYykNCmBgYA0KDQpUYSB0aOG7rSBkw7luZyBow6BtIHRpZHksIHbhu5tpIDIgecOqdSBj4bqndSBwaOG7pToNCg0KMSkgw4FwIGThu6VuZyBow6BtIGV4cG9uZW50aWFsIMSR4buDIHTDrW5oIE9kZHMtcmF0aW9zLg0KDQoyKSBYdeG6pXQgOTcuNSUgQ0kgYuG6sW5nIHBoxrDGoW5nIHBow6FwIGJvb3RzdHJhcCAuDQoNClF1eSB0csOsbmggYm9vdHN0cmFwIG3huqV0IG3hu5l0IMOtdCB0aOG7nWkgZ2lhbiwgc2F1IMSRw6J5IGzDoCBr4bq/dCBxdeG6ozoNCg0KTmjGsCB0YSB0aOG6pXksIGjDoG0gdGlkeSDEkcOjIGLhu48gaOG6v3Qgbmjhu69uZyBjaGkgdGnhur90IHRo4burYSwgcXV5IMSR4buTbmcgbuG7mWkgZHVuZyBtw7QgaMOsbmggduG7gSBjw7luZyBt4buZdCBow6xuaCB0aOG7qWMgdHLDrG5oIGLDoHksIGPhuqV1IHRyw7pjIGLhuqNuZyB0xrDGoW5nIHThu7EgbmjGsCB0cm9uZyBtw7QgaMOsbmggbmxtZSwgY8OhYyB0ZXJtcyDEkcaw4bujYyBwaMOibiB0aMOgbmggbG/huqFpIGVmZmVjdCB2w6AgZ3JvdXA7IHbhu5tpIGdpw6EgdHLhu4sgxJHDoyDEkcaw4bujYyBob8OhbiBjaHV54buDbiB0aMOgbmggT2Rkcy1yYXRpbywgWi12YWx1ZXMgZ2nhu68gbmd1ecOqbixlc3RpbWF0ZSB0xrDGoW5nIOG7qW5nIHbhu5tpdHJ1bmcgYsOsbmggY+G7p2EgT2Rkcy1yYXRpbywgU0UgdsOgIDk3LjUlIENJIGPhu6dhIE9kZHMtcmF0aW8gxJHGsOG7o2MgeMOhYyDEkeG7i25oIGLhurFuZyBib290c3RyYXAuIEvhur90IHF14bqjIG7DoHkgc+G6tW4gc8OgbmcgxJHhu4MgxJHGsGEgdsOgbyBiw6FvIGPDoW8uDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdGlkeShsbWU0X2xvZ2lzdGljLA0KICAgICBleHBvbmVudGlhdGU9VFJVRSwgDQogICAgIGNvbmYuaW50ID0gVCwgDQogICAgIGNvbmYubGV2ZWwgPSAwLjk3NSwgDQogICAgIGNvbmYubWV0aG9kID0iYm9vdCIpDQpgYGANCg0KVGEgdGjhu60gaMOgbSBnbGFuY2UgxJHhu4Mga2jhuqNvIHPDoXQgcGjhuqltIGNo4bqldCBtw7QgaMOsbmg6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZ2xhbmNlKGxtZTRfbG9naXN0aWMpDQpgYGANCg0KVsOgIGjDoG0gYXVnbWVudCDEkeG7gyDGsOG7m2MgdMOtbmggZ2nDoSB0cuG7iyBk4buxIGLDoW8gdHLDqm4gY2jDrW5oIGThu68gbGnhu4d1IGfhu5FjOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmF1Z21lbnQobG1lNF9sb2dpc3RpYyxkYXRhPWNicHApJT4laGVhZCgpDQpgYGANCg0KQsOieSBnaeG7nSBjaMO6bmcgdGEgc+G6vSB0aOG7rSDDoXAgZOG7pW5nIGJyb29tLm1peGVkIGNobyBtw7QgaMOsbmggQmF5ZXMsIGPhu6UgdGjhu4MgbMOgIDIgcGFja2FnZSBicm1zIHbDoCByc3RhbmFybS4gxJDhu4MgdGnhur90IGtp4buHbSB0aOG7nWkgZ2lhbiwgTmhpIHPhur0ga2jDtG5nIHRo4buxYyBz4buxIGTDuW5nIGJybXMgbmjGsG5nIHThuqNpIGThu68gbGnhu4d1IG3hu5l0IG3DtCBow6xuaCBicm1zIGPDsyBz4bq1biB0cm9uZyBwYWNrYWdlIGJyb29tLm1peGVkOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxvYWQoc3lzdGVtLmZpbGUoImV4dGRhdGEiLCAiYnJtc19leGFtcGxlLnJkYSIsIHBhY2thZ2U9ImJyb29tLm1peGVkIikpDQpgYGANCg0KxJDDonkgbMOgIG3hu5l0IG3DtCBow6xuaCBtaXhlZCBtb2RlbCB24bubaSBjcm9zc2VkIHJhbmRvbSBlZmZlY3QgdHLDqm4gZGF0YXNldCBtdGNhcnMNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpmaXQgPC0gYnJtc19jcm9zc2VkUkUNCg0KZml0JGZpdA0KYGBgDQoNCktow6FjIHbhu5tpIGxtZTQgaGF5IG5sbWUsIGJybXMgdGjhu7FjIGNo4bqldCDDoXAgZOG7pW5nIHN1eSBkaeG7hW4gQmF5ZXMgxJHhu4MgdOG6oW8gcmEgbmjhu69uZyBjaHXhu5dpIE1DTUMgY2jhu6lhIHBow6JuIHBo4buRaSBo4bqtdSBuZ2hp4buHbSBjaG8gbeG7l2kgdGhhbSBz4buRICh0ZXJtcywgcGFyYW1ldGVycykgdHJvbmcgbcO0IGjDrG5oLCB2w6Aga+G6v3QgcXXhuqMgY8WpbmcgxJHGsOG7o2MgdHLDrG5oIGLDoHkgdGhlbyBjw6FjaCBraMOhYy4gVHV5IG5oacOqbiBraGkgw6FwIGThu6VuZyBow6BtIHRpZHksIGvhur90IHF14bqjIG3DtCBow6xuaCBCYXllcyBjxaluZyDEkcaw4bujYyB0w7NtIHThuq90IGfhu41uIGzhuqFpIHRoZW8gY8O5bmcgbeG7mXQgY8OhY2ggdGjhu6ljOg0KDQpL4bq/dCBxdeG6oyBuw6B5IGPDsyB0aOG7gyDEkcaw4bujYyBkaeG7hW4gZ2nhuqNpIHTGsMahbmcgdOG7sSBuaMawIGLhuqV0IGPhu6kgbcO0IGjDrG5oIG7DoG8sIGNo4buJIGtow6FjIG3hu5l0IGNow7p0IHbhu4EgdGh14bqtdCBuZ+G7rzoga2hv4bqjbmcgdGluIGPhuq15IHRyb25nIHRo4buRbmcga8OqIEJheWVzIMSRxrDhu6NjIGfhu41pIGzDoCBIaWdoZXN0IHBvc3RlcmlvciBkZW5zaXR5IGludGVydmFsKEhQREkgaGF5IEhESSkuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KYnJtc19zdW09dGlkeShmaXQsDQogICAgIHJvYnVzdD1UUlVFLCANCiAgICAgY29uZi5tZXRob2Q9IkhQRGludGVydmFsIiwgDQogICAgIGNvbmYubGV2ZWwgPSAwLjk3NSkNCg0KYnJtc19zdW0NCmBgYA0KDQpM4bujaSB0aOG6vyBj4bunYSBk4buvIGxp4buHdSBk4bqhbmcgYuG6o25nIG7DoHkgxJHDsyBsw6AgdGEgY8OzIHRo4buDIMSRxrBhIG7DsyB2w6BvIHBpcGUgxJHhu4MgduG6vSBt4buZdCBiaeG7g3UgxJHhu5MgdMawxqFuZyB04buxIG5oxrAgaMOsbmggYsOqbiBkxrDhu5tpOg0KDQpgYGB7cn0NCmJybXNfc3VtJT4lZ2dwbG90KGFlcyhlc3RpbWF0ZSx0ZXJtLHhtaW49Y29uZi5sb3cseG1heD1jb25mLmhpZ2gsY29sPWVzdGltYXRlKSkrDQogIGdlb21fZXJyb3JiYXJoKGhlaWdodD0wKSsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTAsbHR5PTIsY29sPSJibGFjayIpKw0KICBnZW9tX3BvaW50KHNpemU9MykrDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvdXJzID0gcGFsczo6Y29vbHdhcm0oMTApKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCk3DtCBow6xuaCBtaXhlZCBtb2RlbCBCYXllcyBjaG8gcGjDqXAgxrDhu5tjIGzGsOG7o25nIMSRxrDhu6NjIGPhuqMgcGjDom4gcGjhu5FpIGjhuq11IG5naGnhu4dtIMSR4bq/biBt4bupYyDEkeG7mSBjw6EgdGjhu4M6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KYnJtc19yYW5fdmFsPXRpZHkoZml0LCBlZmZlY3RzID0gInJhbl92YWxzIiwgDQogICAgIHJvYnVzdCA9IFQsDQogICAgIGNvbmYubWV0aG9kPSJIUERpbnRlcnZhbCIsIA0KICAgICBjb25mLmxldmVsID0gMC45NzUpIA0KDQpicm1zX3Jhbl92YWwNCmBgYA0KDQpgYGB7cn0NCmJybXNfcmFuX3ZhbCU+JQ0KICBnZ3Bsb3QoYWVzKGVzdGltYXRlLGxldmVsLHhtaW49Y29uZi5sb3cseG1heD1jb25mLmhpZ2gsY29sPWdyb3VwKSkrDQogIGdlb21fZXJyb3JiYXJoKGhlaWdodD0wKSsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTAsbHR5PTIsY29sPSJibGFjayIpKw0KICAgIGZhY2V0X2dyaWQodGVybX5ncm91cCxzY2FsZT0iZnJlZV94IikrDQogIGdlb21fcG9pbnQoc2l6ZT0zKSsNCiAgdGhlbWVfYncoKQ0KDQpgYGANCg0KQsOieSBnaeG7nSB0YSB0aOG7rSBt4buZdCBtw7QgaMOsbmggQmF5ZXMgbWl4ZWQgbW9kZWwga2jDoWMgduG7m2kgcGFja2FnZSByc3RhbmFybToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHJzdGFuYXJtKQ0KDQpzdGFuX2ZpdCA8LSBzdGFuX2dsbWVyKG1wZyB+IHd0ICsgKDF8Y3lsKSArICgxK3d0fGdlYXIpLCBkYXRhID0gbXRjYXJzLA0KICAgICAgICAgICAgICAgICAgaXRlciA9IDMwMCwgY2hhaW5zID0gMikNCg0Kc3VtbWFyeShzdGFuX2ZpdCkNCmBgYA0KDQpO4buZaSBkdW5nIHF1w6EgZMOgaSBkw7JuZyBuw6B5IHPhur0gxJHGsOG7o2MgdMOzbSBn4buNbiBi4bqxbmcgaMOgbSB0aWR5LCB0aGVvIGPDuW5nIG3hu5l0IGPDoWNoIG5oxrAgdGEgxJHDoyBiaeG6v3QNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQp0aWR5KHN0YW5fZml0LCBjb25mLmludCA9IFRSVUUsIGNvbmYubWV0aG9kID0gIkhQRGludGVydmFsIiwgcHJvYiA9IDAuOTUpDQpgYGANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQp0aWR5KHN0YW5fZml0LCBlZmZlY3RzID0gInJhbl9wYXJzIikNCmBgYA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KDQpgYGANCg0KYGBge3J9DQpzdGFuX2ZpdCU+JXRpZHkoZWZmZWN0cyA9ICJyYW5fdmFscyIsDQogICAgICAgICAgICAgICAgY29uZi5pbnQgPSBUUlVFLCANCiAgICAgICAgICAgICAgICBjb25mLm1ldGhvZCA9ICJIUERpbnRlcnZhbCIpJT4lDQogIGdyb3VwX2J5KGdyb3VwLGxldmVsKSAlPiUNCiAgbXV0YXRlKGtlZXA9YW55KGVzdGltYXRlID4gMCkpJT4lDQogIGdncGxvdChhZXMoZXN0aW1hdGUsbGV2ZWwsDQogICAgICAgICAgICAgeG1pbj1jb25mLmxvdyx4bWF4PWNvbmYuaGlnaCwNCiAgICAgICAgICAgICBjb2w9ZmFjdG9yKGtlZXApKSkrDQogIGdlb21fZXJyb3JiYXJoKGhlaWdodD0wKSsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTAsbHR5PTIpKw0KICBnZW9tX3BvaW50KHNpemU9MykrDQogICAgZmFjZXRfZ3JpZCh0ZXJtfmdyb3VwLHNjYWxlPSJmcmVlX3giKSsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygiYmx1ZSIsInJlZCIpLGd1aWRlPUZBTFNFKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCkN14buRaSBjw7luZywgdGEgdGjhu60gZMO5bmcgaMOgbSB0aWR5IGNobyBtw7QgaMOsbmggZOG7sW5nIGLhurFuZyBnYW1sc3MuIE5oxrAgxJHDoyBuw7NpLCBnYW1sc3MgbMOgIG3hu5l0IHBhY2thZ2Uga2jDoSBrw6wgZOG7iywga+G6v3QgcXXhuqMgbcOgIG7DsyB4deG6pXQgcmEga2jDtG5nIGdp4buRbmcgduG7m2kgYuG6pXQgY+G7qSBnw7NpIG7DoG8ga2jDoWMsIHbDoCBy4bqldCBraMOzIGtoYWkgdGjDoWMuVHJvbmcgdGjDrSBk4bulIG7DoHksIHRhIGThu7FuZyBt4buZdCBtw7QgaMOsbmggQm94Q294IHQgduG7m2kgZGF0YXNldCBhYmRvbWVuLkdoaSBjaMO6OiDEkcOieSBraMO0bmcgcGjhuqNpIGzDoCBtaXhlZCBtb2RlbC4NCg0KYGBge3J9DQpkYXRhKGFiZG9tLCBwYWNrYWdlPSJnYW1sc3MuZGF0YSIpIA0KDQpsaWJyYXJ5KGdhbWxzcykNCg0KZ2FtbHNzX21vZDwtIGdhbWxzcyh5fnBiKHgpLHNpZ21hLmZvPX5wYih4KSxmYW1pbHk9QkNULGRhdGE9YWJkb20pDQoNCnN1bW1hcnkoZ2FtbHNzX21vZCkNCmBgYA0KDQpgYGB7cn0NCnRpZHkoZ2FtbHNzX21vZCkNCmBgYA0KDQojIE5o4bqtbiB4w6l0DQoNClBhY2thZ2UgYnJvb20ubWl4ZWQgbMOgIG3hu5l0IGPDtG5nIGPhu6UgcuG6pXQgaOG7r3Ugw61jaCwgbsOzIGdpw7pwIGdp4bqjaSBxdXnhur90IMSRxrDhu6NjIGPDoWMgduG6pW4gxJHhu4EgdHLhu58gbmfhuqFpIGtoaSB0aOG7sWMgaMOgbmggbWl4ZWQgbW9kZWwgYmFvIGfhu5NtOg0KDQoxKSBT4butIGThu6VuZyBjw7luZyBt4buZdCBkYW5oIHBow6FwIHRodeG6rXQgbmfhu68gY2hvIG5o4buvbmcgdGjDoG5oIHBo4bqnbiB0cm9uZyBu4buZaSBkdW5nIG3DtCBow6xuaCwgZMO5IG3DtCBow6xuaCDEkcaw4bujYyBk4buxbmcgYuG6sW5nIHBhY2thZ2UgbsOgby4gQ8OhYyB0ZXJtcyDEkcaw4bujYyBwaMOibiBjaGlhIHRoZW8gdGjhu6kgYuG6rWM6IGVmZmVjdCAoZml4ZWQgaGF5IHJhbmRvbSksIG7hur91IGzDoCByYW5kb20gdGjDrCBsw6AgcGFyYW1ldGVycyBoYXkgdmFsdWVzID8ga+G6vyDEkeG6v24gbMOgIHRlcm1zIChpbnRlcmNlcHQsIGPDoWMgZmFjdG9ycywgcHJlZGljdG9ycyB0cm9uZyBtw7QgaMOsbmgsIHNkIGhheSBjb3JfaW50ZXJjZXB0KSwgZ2nDunAgZOG7hSBkw6BuZyBwaMOibiBiaeG7h3Qgw70gbmdoxKlhIGPhu6dhIGPDoWMgdGjDoG5oIHBo4bqnbiBuw6B5LiANCg0KMikgR2nhuqNuIGzGsOG7o2Mgbmjhu69uZyBjaGkgdGnhur90IHRo4burYSB0aOG6o2kgdHJvbmcgbuG7mWkgZHVuZyBtw7QgaMOsbmgsIGNo4buJIGdp4buvIGzhuqFpIG5o4buvbmcgdGjDtG5nIHRpbiB0aGnhur90IHnhur91IG5o4bqldCDEkeG7gyBzdXkgZGnhu4VuIHRo4buRbmcga8OqDQoNCjMpIFTDoWNoIHJpw6puZyAzIHBo4bqnbiB0aMO0bmcgdGluIHbhu4EgcGjhuqltIGNo4bqldCBtw7QgaMOsbmgsIG7hu5lpIGR1bmcgbcO0IGjDrG5oIHbDoCBnacOhIHRy4buLIGThu7EgYsOhbw0KDQo0KSBRdXkgxJHhu5NuZyB04bqldCBj4bqjIGvhur90IHF14bqjIHThu6sgOC05IHBhY2thZ2Uga2jDoWMgbmhhdSB24buBIGPDuW5nIG3hu5l0IGLhuqNuZyBr4bq/dCBxdeG6oyBjw7MgY+G6pXUgdHLDumMgbmjhuqV0IHF1w6FuLg0KDQo1KSBL4bq/dCBxdeG6oyDEkcaw4bujYyB4deG6pXQgcmEgZMaw4bubaSBk4bqhbmcgdGliYmxlLCBjaG8gcGjDqXAgdGjhu7FjIGhp4buHbiBjw6FjIGjDoG0gcGjDom4gdMOtY2ggc8OidSwgc+G7rSBk4bulbmcgcGlwZWxpbmUgdsOgIHbhur0gYmnhu4N1IMSR4buTIGLhurFuZyBnZ3Bsb3QuDQoNCkLDoGkgdGjhu7FjIGjDoG5oIMSR4bq/biDEkcOieSBsw6AgaOG6v3QuIFThuqFtIGJp4buHdCBjw6FjIGLhuqFuIHbDoCBo4bq5biBn4bq3cCBs4bqhaS4uLg==