1 Giới thiệu

Khi thực hiện project Bayes for Vietnam, Nhi có hy vọng khuyến khích anh chị em đồng nghiệp chuyển sang dùng Thống kê theo trường phái Bayes thay cho những kiểm định rời rạc khác. Qua những bài thực hành trước đây, chúng ta đã thấy khả năng mạnh mẽ và linh hoạt của phương pháp Bayes. Tuy nhiên mục tiêu phổ cập phương pháp này cho số đông sẽ khó thành công nếu buộc người học phải viết code thủ công hoàn toàn cho nội dung mô hình và quy trình khai thác kết quả. Vì lý do này, Nhi sẽ dần chuyển từ cách làm thủ công với chính ngôn ngữ STAN sang sử dụng những công cụ đơn giản hơn, thí dụ package brms.

Như các bạn đã biết, suy diễn thống kê theo Bayes có hai bước, đầu tiên ta phải có một mô hình (bạn có thể dựng mô hình bằng rstan, stanarm hay brms), sau đó từ mô hình này ta có thể khảo sát phân bố hậu nghiệm từ các chuỗi MCMC để kiểm định một giả thuyết tùy chọn.

Một số phương pháp đã được giới thiệu cho bước khai thác mô hình, bao gồm mô tả trực quan phân bố hậu nghiệm, tóm tắt khoảng mật độ cao nhất (Highest density Interval, HDI), kiểm tra giả thuyết vô hiệu với ngưỡng đơn lẻ (Compval) hay khoảng vô hiệu thực dụng (ROPE), giá trị p_value hay Bayes factor. Tuy nhiên, tất cả những quy trình này cần phải viết hàm thủ công.

Trong phiên bản mới nhất của package sjstats, tác giả Daniel Lüdecke đã bổ sung một loạt hàm cho phép suy diễn Bayes trực tiếp từ object mô hình brms và rstanarm. Bài thực hành hôm nay sẽ giới thiệu với các bạn về các hàm này.

library(tidyverse)

library(rstan)
library(brms)

rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())

Trước hết, ta sẽ dựng 2 mô hình hồi quy Bayes bằng brms:

Mô hình thứ nhất là một thiết kế MANCOVA có nội dung như sau: Đây là một thí nghiệm sinh lý trên động vật vào năm 1994 của GS. J. Ludbrook. mục tiêu nhằm khảo sát hiệu ứng tương tác của liều hoạt chất phenylbiguanide (kích thích co mạch) và 1 chất đối vận serotonin (có tác dụng giảm co mạch) gây thay đổi huyết áp ở 5 con thỏ.

Biến kết quả là BPchange=thay đổi huyết áp, yếu tố thứ nhất là Treatment chỉ phân nhóm sử dụng MDL đối kháng thụ thể serotonin và Dose chỉ liều phenylbiguanid. Do thí nghiệm lặp lại 5 lần ở mỗi con vật nên ta có thểm random effect trong mô hình.

mdl_df<-read.csv("http://vincentarelbundock.github.io/Rdatasets/csv/MASS/Rabbit.csv")

head(mdl_df)%>%knitr::kable()
X BPchange Dose Run Treatment Animal
1 0.5 6.25 C1 Control R1
2 4.5 12.50 C1 Control R1
3 10.0 25.00 C1 Control R1
4 26.0 50.00 C1 Control R1
5 37.0 100.00 C1 Control R1
6 32.0 200.00 C1 Control R1
mod<-brm(BPchange~Treatment*Dose+(1|Animal),
          data=mdl_df,
          family = brmsfamily("gaussian", link_sigma = "log"),
          seed = 1234,iter=1500,chains = 1
          )
## 
## SAMPLING FOR MODEL 'gaussian brms-model' NOW (CHAIN 1).
## 
## Gradient evaluation took 0.02 seconds
## 1000 transitions using 10 leapfrog steps per transition would take 200 seconds.
## Adjust your expectations accordingly!
## 
## 
## Iteration:    1 / 1500 [  0%]  (Warmup)
## Iteration:  150 / 1500 [ 10%]  (Warmup)
## Iteration:  300 / 1500 [ 20%]  (Warmup)
## Iteration:  450 / 1500 [ 30%]  (Warmup)
## Iteration:  600 / 1500 [ 40%]  (Warmup)
## Iteration:  750 / 1500 [ 50%]  (Warmup)
## Iteration:  751 / 1500 [ 50%]  (Sampling)
## Iteration:  900 / 1500 [ 60%]  (Sampling)
## Iteration: 1050 / 1500 [ 70%]  (Sampling)
## Iteration: 1200 / 1500 [ 80%]  (Sampling)
## Iteration: 1350 / 1500 [ 90%]  (Sampling)
## Iteration: 1500 / 1500 [100%]  (Sampling)
## 
##  Elapsed Time: 0.384 seconds (Warm-up)
##                0.12 seconds (Sampling)
##                0.504 seconds (Total)
summary(mod)
##  Family: gaussian 
##   Links: mu = identity; sigma = identity 
## Formula: BPchange ~ Treatment * Dose + (1 | Animal) 
##    Data: mdl_df (Number of observations: 60) 
## Samples: 1 chains, each with iter = 1500; warmup = 750; thin = 1;
##          total post-warmup samples = 750
## 
## Group-Level Effects: 
## ~Animal (Number of levels: 5) 
##               Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
## sd(Intercept)     2.83      1.99     0.34     8.84         86 1.00
## 
## Population-Level Effects: 
##                   Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
## Intercept             4.26      2.41     0.28    11.44         76 1.03
## TreatmentMDL         -4.22      2.18    -8.36     0.47        330 1.00
## Dose                  0.14      0.02     0.11     0.18        274 1.00
## TreatmentMDL:Dose    -0.01      0.02    -0.05     0.04        376 1.00
## 
## Family Specific Parameters: 
##       Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
## sigma     5.89      0.57     4.92     7.10        473 1.00
## 
## Samples were drawn using sampling(NUTS). For each parameter, Eff.Sample 
## is a crude measure of effective sample size, and Rhat is the potential 
## scale reduction factor on split chains (at convergence, Rhat = 1).

Thứ hai là một mô hình logistic với phân bố bernoulli (link function là hàm logit), để khảo sát liên hệ giữa các yếu tố: Giới tính, bệnh Ung thư, Suy thận, Nhiễm trùng, Gãy xương, kỹ thuật CPR đối với nguy cơ tử vong của bệnh nhân cấp cứu tại khoa ICU.

icu_df<-read.csv("https://www.openml.org/data/get_csv/53980/ICU.csv")%>%select(STA,SEX,CAN,CRN,INF,FRA)

icu_df$CAN<-icu_df$CAN%>%recode_factor(.,"1"="NO","2"="YES")
icu_df$CRN<-icu_df$CRN%>%recode_factor(.,"1"="NO","2"="YES")
icu_df$INF<-icu_df$INF%>%recode_factor(.,"1"="NO","2"="YES")
icu_df$FRA<-icu_df$FRA%>%recode_factor(.,"1"="NO","2"="YES")
icu_df$SEX<-icu_df$SEX%>%recode_factor(.,"1"="M","2"="F")

head(icu_df)%>%knitr::kable()
STA SEX CAN CRN INF FRA
0 F NO NO YES NO
0 M NO NO NO NO
0 M NO NO NO NO
0 M NO NO YES YES
0 F NO NO YES NO
0 M NO NO YES NO
logmod=brm(STA~CAN+CRN+INF+FRA+SEX,
            family=bernoulli(link = "logit"),data=icu_df,
            seed = 1234,iter=1500,chains = 1
            )
## 
## SAMPLING FOR MODEL 'bernoulli brms-model' 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 / 1500 [  0%]  (Warmup)
## Iteration:  150 / 1500 [ 10%]  (Warmup)
## Iteration:  300 / 1500 [ 20%]  (Warmup)
## Iteration:  450 / 1500 [ 30%]  (Warmup)
## Iteration:  600 / 1500 [ 40%]  (Warmup)
## Iteration:  750 / 1500 [ 50%]  (Warmup)
## Iteration:  751 / 1500 [ 50%]  (Sampling)
## Iteration:  900 / 1500 [ 60%]  (Sampling)
## Iteration: 1050 / 1500 [ 70%]  (Sampling)
## Iteration: 1200 / 1500 [ 80%]  (Sampling)
## Iteration: 1350 / 1500 [ 90%]  (Sampling)
## Iteration: 1500 / 1500 [100%]  (Sampling)
## 
##  Elapsed Time: 0.262 seconds (Warm-up)
##                0.161 seconds (Sampling)
##                0.423 seconds (Total)
summary(logmod)
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: STA ~ CAN + CRN + INF + FRA + SEX 
##    Data: icu_df (Number of observations: 200) 
## Samples: 1 chains, each with iter = 1500; warmup = 750; thin = 1;
##          total post-warmup samples = 750
## 
## Population-Level Effects: 
##           Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
## Intercept    -2.02      0.34    -2.71    -1.38        750 1.00
## CANYES        0.07      0.61    -1.22     1.24        750 1.00
## CRNYES        1.13      0.52     0.13     2.09        750 1.00
## INFYES        0.89      0.39     0.17     1.64        750 1.00
## FRAYES       -0.05      0.74    -1.50     1.25        750 1.00
## SEXF          0.03      0.38    -0.72     0.72        750 1.00
## 
## Samples were drawn using sampling(NUTS). For each parameter, Eff.Sample 
## is a crude measure of effective sample size, and Rhat is the potential 
## scale reduction factor on split chains (at convergence, Rhat = 1).

Tiếp theo, các bạn install 2 package sjstats và sjPlot vào R, đây là 2 công cụ rất tiện dụng cho thực hành thống kê. Chúng cung cấp các hàm cho phép đơn giản hóa nhiều quy trình khai thác kết quả của mô hình trong R.

library(sjstats)

library(sjPlot)

2 Hàm hdi(): khảo sát phân phối hậu nghiệm

Hàm hdi() có công dụng tóm tắt thông tin về phân phối hậu nghiệm của các tham số trong mô hình (hay hiệu ứng ta cần khảo sát). HDI là một thuật ngữ của John Kruschke, với ý nghĩa gần giống như Confidence Interval trong thống kê cổ điển. Kruschke khuyến cáo dùng thuật ngữ Highest density interval để mô tả đúng bản chất mật độ một phân phối liên tục thực sự của hiệu ứng từ chuỗi MCMC, thay cho ý nghĩa “khoảng tin cậy” là kết quả ước lượng từ sự lấy mẫu ngẫu nhiên lặp lại trong phái Frequentist.

Ta có thể tùy chọn ngưỡng cho HDI: thí dụ 90% hay 95%

hdi(mod,prob=c(0.90, 0.95, 0.975),type="fixed")%>%print(digits=3)
## 
## # Highest Density Interval
## 
##                             HDI(90%)        HDI(95%)        HDI(98%)
##  b_Intercept         [ 0.441  7.257] [-0.687  7.907] [ 0.087 12.591]
##  b_TreatmentMDL      [-7.997 -0.740] [-8.690 -0.080] [-8.566  1.245]
##  b_Dose              [ 0.116  0.169] [ 0.114  0.175] [ 0.106  0.178]
##  b_TreatmentMDL.Dose [-0.048  0.029] [-0.055  0.038] [-0.057  0.051]
##  sigma               [ 4.952  6.775] [ 4.759  6.875] [ 4.711  7.161]

Cho mô hình hồi quy logistic, ta có thể áp dụng một hàm hoán chuyển exp( )để có HDI cho Odds-ratio:

hdi(logmod,prob=c(0.8, 0.9, 0.95),type="fixed",trans="exp")%>%print(digits=3)
## 
## # Highest Density Interval
## 
##                   HDI(80%)      HDI(90%)      HDI(95%)
##  b_Intercept [0.086 0.203] [0.074 0.223] [0.067 0.253]
##  b_CANYES    [0.543 2.340] [0.394 2.850] [0.311 3.648]
##  b_CRNYES    [1.556 6.014] [1.461 8.099] [1.165 8.301]
##  b_INFYES    [1.529 3.977] [1.362 4.792] [1.207 5.329]
##  b_FRAYES    [0.406 2.560] [0.327 3.410] [0.223 3.505]
##  b_SEXF      [0.665 1.712] [0.586 1.978] [0.484 2.054]

Nhận xét: hàm hdi() rất tiện dụng cho mục tiêu khảo sát nhanh ý nghĩa thống kê của phân phối hậu nghiệm. So với những hàm tóm tắt chuỗi MCMC của brms, broom hay coda, hàm hdi linh hoạt hơn khi cho phép người dùng ấn định nhiều ngưỡng probability; tuy nhiên nó không cung cấp giá trị trung tâm (trung bình, trung vị) của phân bố này.

3 Hàm rope(): khoảng vô hiệu thực dụng

Hàm rope() cho phép áp dụng một khoảng tùy chọn [-b,b] với -b và b là hai ngưỡng trên và dưới, với giả định là phần phân phối hậu nghiệm rơi vào bên trong khoảng này sẽ được xem như vô hiệu (hay không có ý nghĩa) về mặt thực dụng. Thí dụ nếu Odds-ratio nằm trong khoảng [-1.5,1.5 ]cho biết ta chỉ chấp nhận OR có ý nghĩa nếu giá trị của nó > 1.5 hay <-1.5.

Thí dụ cho mô hình MANCOVA, ta đặt rope = -2 đến 1, kết quả như sau

rope(mod,rope=c(-2,1),type="fixed")%>%print(digits=3)
## 
## # Proportions of samples inside and outside the ROPE
## 
##                        inside  outside
##  b_Intercept           6.667%  93.333%
##  b_TreatmentMDL       13.467%  86.533%
##  b_Dose              100.000%   0.000%
##  b_TreatmentMDL.Dose 100.000%   0.000%
##  sigma                 0.000% 100.000%

Kết quả này được diễn giải là: Hoạt chất MDL làm giảm huyết áp với hơn 86 % phân phối hậu nghiệm nằm ngoài ngưỡng -2

Nhận xét: So với code thủ công, hàm rope của package sjstats không xác định được tỉ lệ mật độ phân bố nằm trên và dưới khoảng rope, nhưng chỉ mới tính được bao nhiêu % nằm trong hay ngoài ROPE.

Tương tự, ta áp dụng ROPE=-2 đến 2 cho OR của mô hình logistic như sau:

rope(logmod,rope=c(-2,2),trans="exp")%>%print(digits=3)
## 
## # Proportions of samples inside and outside the ROPE
## 
##                inside outside
##  b_Intercept 100.000%  0.000%
##  b_CANYES     84.267% 15.733%
##  b_CRNYES     20.267% 79.733%
##  b_INFYES     31.600% 68.400%
##  b_FRAYES     84.800% 15.200%
##  b_SEXF       97.067%  2.933%

Theo kết quả này, chỉ có yếu tố Bệnh lý Thận mạn tính là có OR đáng kể nhất (80% nằm ngoài ngưỡng -2:2).

4 Hàm Equivalence test: kiểm định giả thuyết vô hiệu

Hàm Equivalence test thực hiện một kiểm định để suy diễn thống kê tự động cho mỗi hiệu ứng trong mô hình Bayes, dựa vào 1 khoảng vô hiệu thực dụng chung. Giả thuyết cần kiểm định là liệu phân bố hậu nghiệm có nằm ngoài ROPE hay không ? Kết quả có thể là: Chấp nhận giả thuyết, bác bỏ giả thuyết hoặc Hoài nghi, không thể quyết định.

equi_test(mod,out="plot",rope=c(-2,-1),eff_size = 0.1)

equi_test(mod,out="txt",rope=c(-2,-1),eff_size = 0.1)
## 
## # Test for Practical Equivalence of Model Predictors
## 
##   Effect Size: 0.10
##          ROPE: [-2.00 -1.00]
##       Samples: 750
## 
##                                 H0 %inROPE      HDI(95%)
##  b_Intercept (*)            reject    0.00 [-0.69  7.91]
##  b_TreatmentMDL (*)      undecided    7.73 [-8.69 -0.08]
##  b_Dose (*)                 reject    0.00 [ 0.11  0.18]
##  b_TreatmentMDL.Dose (*)    reject    0.00 [-0.06  0.04]
##  sigma                      reject    0.00 [ 4.76  6.87]

Tương tự, ta có thể suy diễn thống kê cho OR của mô hình logistic

equi_test(logmod,out="plot",rope=c(-1,1),eff_size = 0.1,trans="exp")

equi_test(logmod,out="txt",rope=c(-1,1),eff_size = 0.1,trans="exp")
## 
## # Test for Practical Equivalence of Model Predictors
## 
##   Effect Size: 0.10
##          ROPE: [-1.00 1.00]
##       Samples: 750
## 
##                     H0 %inROPE      HDI(95%)
##  b_Intercept    reject    0.00 [-2.71 -1.38]
##  b_CANYES    undecided   89.87 [-1.17  1.29]
##  b_CRNYES    undecided   40.67 [ 0.15  2.12]
##  b_INFYES    undecided   60.27 [ 0.19  1.67]
##  b_FRAYES    undecided   84.00 [-1.50  1.25]
##  b_SEXF         accept   99.07 [-0.73  0.72]

Nhận xét: Đây là một ý tưởng thú vị, vì kết quả của kiểm định có thể giúp đưa ra quyết định về “ý nghĩa thống kê” của hiệu ứng trong mô hình, dựa vào ROPE. Tuy nhiên, nhược điểm của cách làm này đó là chỉ có 1 ngưỡng vô hiệu chung cho mọi hiệu ứng. Phương pháp suy diễn bằng Bayes factor linh hoạt hơn nhiều. Tuy biểu đồ phân bố hậu nghiệm được cung cấp, nó có phẩm chất không tốt bằng các biểu đồ được vẽ thủ công từ ggplot2 và dữ liệu MCMC thô.

5 Hàm tidy_stan: Tóm tắt mô hình

Hàm tidy_stan cho phép tóm tắt dễ dàng nội dung mô hình , bản thân nó bao gồm cả công dụng của hàm hdi nhưng bổ sung thêm giá trị Trung vị của phân bố hậu nghiệm, và 3 trị số kiểm tra phẩm chất chuỗi MCMC:

tidy_stan(mod,prob=0.95)
## 
## # Summary Statistics of Stan-Model
## 
##                    estimate std.error      HDI(95%) neff_ratio Rhat mcse
##  Intercept             4.23      2.02 [-0.69  7.91]       0.10 1.03 0.28
##  TreatmentMDL         -4.22      2.06 [-8.69 -0.08]       0.44 1.00 0.12
##  Dose                  0.14      0.02 [ 0.11  0.18]       0.36 1.00 0.00
##  TreatmentMDL.Dose    -0.01      0.02 [-0.06  0.04]       0.50 1.00 0.00
##  sigma                 5.87      0.55 [ 4.76  6.87]       0.63 1.00 0.03

Ngoài hiệu ứng chính ta còn có thể tóm tắt random effects

tidy_stan(mod,prob=0.95,type="random")
## 
## # Summary Statistics of Stan-Model
## 
## ## Random effect (Intercept)
## 
##            estimate std.error     HDI(95%) neff_ratio Rhat mcse
##  Animal.R1     2.31      2.09 [-0.93 8.10]       0.12 1.02 0.26
##  Animal.R2    -0.41      1.58 [-6.15 3.64]       0.11 1.01 0.25
##  Animal.R3    -0.12      1.50 [-4.83 3.89]       0.09 1.03 0.26
##  Animal.R4     0.11      1.61 [-4.43 4.76]       0.09 1.03 0.28
##  Animal.R5    -1.66      1.85 [-7.40 1.69]       0.09 1.02 0.28

Với mô hình logistic, ta có thể áp dụng hàm exp() để tính OR trực tiếp và tóm tắt kết quả này:

tidy_stan(logmod,prob=0.95,trans="exp")
## 
## # Summary Statistics of Stan-Model
## 
##            estimate std.error    HDI(95%) neff_ratio Rhat mcse
##  Intercept     0.13      0.35 [0.07 0.25]          1    1 0.01
##  CANYES        1.11      0.59 [0.31 3.65]          1    1 0.02
##  CRNYES        3.11      0.55 [1.17 8.30]          1    1 0.02
##  INFYES        2.42      0.40 [1.21 5.33]          1    1 0.01
##  FRAYES        0.98      0.71 [0.22 3.50]          1    1 0.03
##  SEXF          1.03      0.38 [0.48 2.05]          1    1 0.01

Nhận xét: Hàm tidy_stan có nhiều ưu điểm, như dễ sử dụng, chỉ giữ lại những kết quả chính yếu nhất của nội dung mô hình, khả năng hoán chuyển dữ liệu trực tiếp cho phép tính Incidence rate ratio (IRR) cho count data hay Odds-ratio cho logistic model.

Hàm tidyMCMC của package broom cũng có khả năng tóm tắt nội dung mô hình Bayes nhưng cú pháp của nó phức tạp hơn nhiều

broom::tidyMCMC(mod,conf.int = TRUE, 
                estimate.method = "median", 
                conf.method = "HPDinterval",
                pars=c("b_Intercept",
                       "b_TreatmentMDL",
                       "b_Dose",
                       "b_TreatmentMDL:Dose",
                       "sigma"))
##                  term     estimate  std.error    conf.low   conf.high
## 1         b_Intercept  4.225200073 2.41111699 -0.48124134  8.03849857
## 2      b_TreatmentMDL -4.217310163 2.17556023 -8.56608659 -0.07976323
## 3              b_Dose  0.143562406 0.01654160  0.11445559  0.17534723
## 4 b_TreatmentMDL:Dose -0.007982953 0.02375522 -0.05529648  0.03753917
## 5               sigma  5.869718744 0.57264430  4.75905840  6.87125379

6 Hàm plot_model: khảo sát trực quan

Ngoài khả năng tóm tắt nội dung mô hình, package sjPlot còn có một hàm rất tiện dụng khác là plot_model, hàm này cho phép mô tả trực quan tất cả các loại mô hình thông dụng trong R, bao gồm mô hình STAN và brms. Nó sẽ vẽ một biểu đồ boxplot mô tả phân bố hậu nghiệm cho mỗi hiệu ứng trong mô hình:

plot_model(mod,
           vline.color = "red",
           sort.est = TRUE,
           show.values = TRUE, 
           value.offset = .3)+theme_bw()

plot_model(mod,type="re",
           vline.color = "red",
           sort.est = TRUE,
           show.values = TRUE, 
           value.offset = .3)+theme_bw()

Khi áp dụng cho mô hình logistic, ta chỉ cần thêm hàm hoán chuyển exp trong tùy chỉnh trans để vẽ biểu đồ cho OR:

plot_model(logmod,trans="exp",
           vline.color = "red",
           sort.est = TRUE,
           show.values = TRUE, 
           value.offset = .3,
           show.p=TRUE)+theme_bw()

Nhận xét: Biểu đồ trực quan cho mô hình Bayes sử dụng Boxplot thay vì point range plot vì ta đang khảo sát 1 phân bố liên tục thực sự của mỗi tham số trong mô hình. Các biểu đồ này giản dị và vừa đủ cho việc công bố kết quả. Tuy nhiên, phẩm chất mỹ thuật của chúng không thể so sánh được với các biểu đồ được dựng thủ công, nhất là density plot của package ggridges.

7 Kết luận

Vào năm 2016 có người khẳng định lạc quan rằng “Sớm muộn gì tất cả người dùng R đều sẽ theo trường phái Bayes”. Điều này có thể tin được nếu ta biết rằng hiện nay phương pháp suy diễn Bayes đã trở nên dễ dàng và phổ biến hơn bao giờ hết trong R. Ngôn ngữ STAN trở thành nền tảng chung cho thống kê Bayes, ít nhất 3 package trong R cho phép kết nối với STAN, trong đó brms là công cụ đơn giản và mạnh nhất. Những packages như sjstats và sjPlot đã cung cấp cho người dùng mảnh ghép cuối cùng còn thiếu - khai thác mô hình Bayes. Với những hàm được giới thiệu trong bài, ta hoàn toàn có thể hy vọng về một tương lai rất rộng mở cho trường phái Bayes, từng bước thay thế cho những phương pháp kiểm định cổ điển khác.

LS0tDQp0aXRsZTogIk3huqNuaCBnaMOpcCBjdeG7kWkgY8O5bmcgY2hvIHN1eSBkaeG7hW4gQmF5ZXMiDQphdXRob3I6ICJMw6ogTmfhu41jIEto4bqjIE5oaSINCmRhdGU6ICIxMyBUaMOhbmcgNiAyMDE4Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImRlZmF1bHQiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQohW10oYmF5ZXNmdW5jczEucG5nKQ0KDQojIEdp4bubaSB0aGnhu4d1DQoNCktoaSB0aOG7sWMgaGnhu4duIHByb2plY3QgQmF5ZXMgZm9yIFZpZXRuYW0sIE5oaSBjw7MgaHkgduG7jW5nIGtodXnhur9uIGtow61jaCBhbmggY2jhu4sgZW0gxJHhu5NuZyBuZ2hp4buHcCBjaHV54buDbiBzYW5nIGTDuW5nIFRo4buRbmcga8OqIHRoZW8gdHLGsOG7nW5nIHBow6FpIEJheWVzIHRoYXkgY2hvIG5o4buvbmcga2nhu4NtIMSR4buLbmggcuG7nWkgcuG6oWMga2jDoWMuIFF1YSBuaOG7r25nIGLDoGkgdGjhu7FjIGjDoG5oIHRyxrDhu5tjIMSRw6J5LCBjaMO6bmcgdGEgxJHDoyB0aOG6pXkga2jhuqMgbsSDbmcgbeG6oW5oIG3hur0gdsOgIGxpbmggaG/huqF0IGPhu6dhIHBoxrDGoW5nIHBow6FwIEJheWVzLiBUdXkgbmhpw6puIG3hu6VjIHRpw6p1IHBo4buVIGPhuq1wIHBoxrDGoW5nIHBow6FwIG7DoHkgY2hvIHPhu5EgxJHDtG5nIHPhur0ga2jDsyB0aMOgbmggY8O0bmcgbuG6v3UgYnXhu5ljIG5nxrDhu51pIGjhu41jIHBo4bqjaSB2aeG6v3QgY29kZSB0aOG7pyBjw7RuZyBob8OgbiB0b8OgbiBjaG8gbuG7mWkgZHVuZyBtw7QgaMOsbmggdsOgIHF1eSB0csOsbmgga2hhaSB0aMOhYyBr4bq/dCBxdeG6oy4gVsOsIGzDvSBkbyBuw6B5LCBOaGkgc+G6vSBk4bqnbiBjaHV54buDbiB04burIGPDoWNoIGzDoG0gdGjhu6cgY8O0bmcgduG7m2kgY2jDrW5oIG5nw7RuIG5n4buvIFNUQU4gc2FuZyBz4butIGThu6VuZyBuaOG7r25nIGPDtG5nIGPhu6UgxJHGoW4gZ2nhuqNuIGjGoW4sIHRow60gZOG7pSBwYWNrYWdlIGJybXMuDQoNCk5oxrAgY8OhYyBi4bqhbiDEkcOjIGJp4bq/dCwgc3V5IGRp4buFbiB0aOG7kW5nIGvDqiB0aGVvIEJheWVzIGPDsyBoYWkgYsaw4bubYywgxJHhuqd1IHRpw6puIHRhIHBo4bqjaSBjw7MgbeG7mXQgbcO0IGjDrG5oIChi4bqhbiBjw7MgdGjhu4MgZOG7sW5nIG3DtCBow6xuaCBi4bqxbmcgcnN0YW4sIHN0YW5hcm0gaGF5IGJybXMpLCBzYXUgxJHDsyB04burIG3DtCBow6xuaCBuw6B5IHRhIGPDsyB0aOG7gyBraOG6o28gc8OhdCBwaMOibiBi4buRIGjhuq11IG5naGnhu4dtIHThu6sgY8OhYyBjaHXhu5dpIE1DTUMgxJHhu4Mga2nhu4NtIMSR4buLbmggbeG7mXQgZ2nhuqMgdGh1eeG6v3QgdMO5eSBjaOG7jW4uDQoNCk3hu5l0IHPhu5EgcGjGsMahbmcgcGjDoXAgxJHDoyDEkcaw4bujYyBnaeG7m2kgdGhp4buHdSBjaG8gYsaw4bubYyBraGFpIHRow6FjIG3DtCBow6xuaCwgYmFvIGfhu5NtIG3DtCB04bqjIHRy4buxYyBxdWFuIHBow6JuIGLhu5EgaOG6rXUgbmdoaeG7h20sIHTDs20gdOG6r3Qga2hv4bqjbmcgbeG6rXQgxJHhu5kgY2FvIG5o4bqldCAoSGlnaGVzdCBkZW5zaXR5IEludGVydmFsLCBIREkpLCBraeG7g20gdHJhIGdp4bqjIHRodXnhur90IHbDtCBoaeG7h3UgduG7m2kgbmfGsOG7oW5nIMSRxqFuIGzhursgKENvbXB2YWwpIGhheSBraG/huqNuZyB2w7QgaGnhu4d1IHRo4buxYyBk4bulbmcgKFJPUEUpLCBnacOhIHRy4buLIHBfdmFsdWUgaGF5IEJheWVzIGZhY3Rvci4gVHV5IG5oacOqbiwgdOG6pXQgY+G6oyBuaOG7r25nIHF1eSB0csOsbmggbsOgeSBj4bqnbiBwaOG6o2kgdmnhur90IGjDoG0gdGjhu6cgY8O0bmcuDQoNClRyb25nIHBoacOqbiBi4bqjbiBt4bubaSBuaOG6pXQgY+G7p2EgcGFja2FnZSBzanN0YXRzLCB0w6FjIGdp4bqjIERhbmllbCBMw7xkZWNrZSDEkcOjIGLhu5Ugc3VuZyBt4buZdCBsb+G6oXQgaMOgbSBjaG8gcGjDqXAgc3V5IGRp4buFbiBCYXllcyB0cuG7sWMgdGnhur9wIHThu6sgb2JqZWN0IG3DtCBow6xuaCBicm1zIHbDoCByc3RhbmFybS4gQsOgaSB0aOG7sWMgaMOgbmggaMO0bSBuYXkgc+G6vSBnaeG7m2kgdGhp4buHdSB24bubaSBjw6FjIGLhuqFuIHbhu4EgY8OhYyBow6BtIG7DoHkuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCmxpYnJhcnkocnN0YW4pDQpsaWJyYXJ5KGJybXMpDQoNCnJzdGFuX29wdGlvbnMoYXV0b193cml0ZSA9IFRSVUUpDQpvcHRpb25zKG1jLmNvcmVzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkpDQpgYGANCg0KVHLGsOG7m2MgaOG6v3QsIHRhIHPhur0gZOG7sW5nIDIgbcO0IGjDrG5oIGjhu5NpIHF1eSBCYXllcyBi4bqxbmcgYnJtczoNCg0KTcO0IGjDrG5oIHRo4bupIG5o4bqldCBsw6AgbeG7mXQgdGhp4bq/dCBr4bq/IE1BTkNPVkEgY8OzIG7hu5lpIGR1bmcgbmjGsCBzYXU6IMSQw6J5IGzDoCBt4buZdCB0aMOtIG5naGnhu4dtIHNpbmggbMO9IHRyw6puIMSR4buZbmcgduG6rXQgdsOgbyBuxINtIDE5OTQgY+G7p2EgR1MuIEouIEx1ZGJyb29rLiBt4bulYyB0acOqdSBuaOG6sW0ga2jhuqNvIHPDoXQgaGnhu4d1IOG7qW5nIHTGsMahbmcgdMOhYyBj4bunYSBsaeG7gXUgaG/huqF0IGNo4bqldCBwaGVueWxiaWd1YW5pZGUgKGvDrWNoIHRow61jaCBjbyBt4bqhY2gpIHbDoCAxIGNo4bqldCDEkeG7kWkgduG6rW4gc2Vyb3RvbmluIChjw7MgdMOhYyBk4bulbmcgZ2nhuqNtIGNvIG3huqFjaCkgZ8OieSB0aGF5IMSR4buVaSBodXnhur90IMOhcCDhu58gNSBjb24gdGjhu48uDQoNCkJp4bq/biBr4bq/dCBxdeG6oyBsw6AgQlBjaGFuZ2U9dGhheSDEkeG7lWkgaHV54bq/dCDDoXAsIHnhur91IHThu5EgdGjhu6kgbmjhuqV0IGzDoCBUcmVhdG1lbnQgY2jhu4kgcGjDom4gbmjDs20gc+G7rSBk4bulbmcgTURMIMSR4buRaSBraMOhbmcgdGjhu6UgdGjhu4Mgc2Vyb3RvbmluIHbDoCBEb3NlIGNo4buJIGxp4buBdSBwaGVueWxiaWd1YW5pZC4gRG8gdGjDrSBuZ2hp4buHbSBs4bq3cCBs4bqhaSA1IGzhuqduIOG7nyBt4buXaSBjb24gduG6rXQgbsOqbiB0YSBjw7MgdGjhu4NtIHJhbmRvbSBlZmZlY3QgdHJvbmcgbcO0IGjDrG5oLg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KbWRsX2RmPC1yZWFkLmNzdigiaHR0cDovL3ZpbmNlbnRhcmVsYnVuZG9jay5naXRodWIuaW8vUmRhdGFzZXRzL2Nzdi9NQVNTL1JhYmJpdC5jc3YiKQ0KDQpoZWFkKG1kbF9kZiklPiVrbml0cjo6a2FibGUoKQ0KDQptb2Q8LWJybShCUGNoYW5nZX5UcmVhdG1lbnQqRG9zZSsoMXxBbmltYWwpLA0KICAgICAgICAgIGRhdGE9bWRsX2RmLA0KICAgICAgICAgIGZhbWlseSA9IGJybXNmYW1pbHkoImdhdXNzaWFuIiwgbGlua19zaWdtYSA9ICJsb2ciKSwNCiAgICAgICAgICBzZWVkID0gMTIzNCxpdGVyPTE1MDAsY2hhaW5zID0gMQ0KICAgICAgICAgICkNCg0Kc3VtbWFyeShtb2QpDQpgYGANCg0KVGjhu6kgaGFpIGzDoCBt4buZdCBtw7QgaMOsbmggbG9naXN0aWMgduG7m2kgcGjDom4gYuG7kSBiZXJub3VsbGkgKGxpbmsgZnVuY3Rpb24gbMOgIGjDoG0gbG9naXQpLCDEkeG7gyBraOG6o28gc8OhdCBsacOqbiBo4buHIGdp4buvYSBjw6FjIHnhur91IHThu5E6IEdp4bubaSB0w61uaCwgYuG7h25oIFVuZyB0aMawLCBTdXkgdGjhuq1uLCBOaGnhu4VtIHRyw7luZywgR8OjeSB4xrDGoW5nLCBr4bu5IHRodeG6rXQgQ1BSIMSR4buRaSB24bubaSBuZ3V5IGPGoSB04butIHZvbmcgY+G7p2EgYuG7h25oIG5ow6JuIGPhuqVwIGPhu6l1IHThuqFpIGtob2EgSUNVLg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmljdV9kZjwtcmVhZC5jc3YoImh0dHBzOi8vd3d3Lm9wZW5tbC5vcmcvZGF0YS9nZXRfY3N2LzUzOTgwL0lDVS5jc3YiKSU+JXNlbGVjdChTVEEsU0VYLENBTixDUk4sSU5GLEZSQSkNCg0KaWN1X2RmJENBTjwtaWN1X2RmJENBTiU+JXJlY29kZV9mYWN0b3IoLiwiMSI9Ik5PIiwiMiI9IllFUyIpDQppY3VfZGYkQ1JOPC1pY3VfZGYkQ1JOJT4lcmVjb2RlX2ZhY3RvciguLCIxIj0iTk8iLCIyIj0iWUVTIikNCmljdV9kZiRJTkY8LWljdV9kZiRJTkYlPiVyZWNvZGVfZmFjdG9yKC4sIjEiPSJOTyIsIjIiPSJZRVMiKQ0KaWN1X2RmJEZSQTwtaWN1X2RmJEZSQSU+JXJlY29kZV9mYWN0b3IoLiwiMSI9Ik5PIiwiMiI9IllFUyIpDQppY3VfZGYkU0VYPC1pY3VfZGYkU0VYJT4lcmVjb2RlX2ZhY3RvciguLCIxIj0iTSIsIjIiPSJGIikNCg0KaGVhZChpY3VfZGYpJT4la25pdHI6OmthYmxlKCkNCg0KbG9nbW9kPWJybShTVEF+Q0FOK0NSTitJTkYrRlJBK1NFWCwNCiAgICAgICAgICAgIGZhbWlseT1iZXJub3VsbGkobGluayA9ICJsb2dpdCIpLGRhdGE9aWN1X2RmLA0KICAgICAgICAgICAgc2VlZCA9IDEyMzQsaXRlcj0xNTAwLGNoYWlucyA9IDENCiAgICAgICAgICAgICkNCg0Kc3VtbWFyeShsb2dtb2QpDQpgYGANCg0KVGnhur9wIHRoZW8sIGPDoWMgYuG6oW4gaW5zdGFsbCAyIHBhY2thZ2Ugc2pzdGF0cyB2w6Agc2pQbG90IHbDoG8gUiwgxJHDonkgbMOgIDIgY8O0bmcgY+G7pSBy4bqldCB0aeG7h24gZOG7pW5nIGNobyB0aOG7sWMgaMOgbmggdGjhu5FuZyBrw6ouIENow7puZyBjdW5nIGPhuqVwIGPDoWMgaMOgbSBjaG8gcGjDqXAgxJHGoW4gZ2nhuqNuIGjDs2Egbmhp4buBdSBxdXkgdHLDrG5oIGtoYWkgdGjDoWMga+G6v3QgcXXhuqMgY+G7p2EgbcO0IGjDrG5oIHRyb25nIFIuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShzanN0YXRzKQ0KDQpsaWJyYXJ5KHNqUGxvdCkNCmBgYA0KDQojIEjDoG0gaGRpKCk6IGto4bqjbyBzw6F0IHBow6JuIHBo4buRaSBo4bqtdSBuZ2hp4buHbQ0KDQpIw6BtIGhkaSgpIGPDsyBjw7RuZyBk4bulbmcgdMOzbSB04bqvdCB0aMO0bmcgdGluIHbhu4EgcGjDom4gcGjhu5FpIGjhuq11IG5naGnhu4dtIGPhu6dhIGPDoWMgdGhhbSBz4buRIHRyb25nIG3DtCBow6xuaCAoaGF5IGhp4buHdSDhu6luZyB0YSBj4bqnbiBraOG6o28gc8OhdCkuIEhESSBsw6AgbeG7mXQgdGh14bqtdCBuZ+G7ryBj4bunYSBKb2huIEtydXNjaGtlLCB24bubaSDDvSBuZ2jEqWEgZ+G6p24gZ2nhu5FuZyBuaMawIENvbmZpZGVuY2UgSW50ZXJ2YWwgdHJvbmcgdGjhu5FuZyBrw6ogY+G7lSDEkWnhu4NuLiBLcnVzY2hrZSBraHV54bq/biBjw6FvIGTDuW5nIHRodeG6rXQgbmfhu68gSGlnaGVzdCBkZW5zaXR5IGludGVydmFsIMSR4buDIG3DtCB04bqjIMSRw7puZyBi4bqjbiBjaOG6pXQgbeG6rXQgxJHhu5kgbeG7mXQgcGjDom4gcGjhu5FpIGxpw6puIHThu6VjIHRo4buxYyBz4buxIGPhu6dhIGhp4buHdSDhu6luZyB04burIGNodeG7l2kgTUNNQywgdGhheSBjaG8gw70gbmdoxKlhICJraG/huqNuZyB0aW4gY+G6rXkiIGzDoCBr4bq/dCBxdeG6oyDGsOG7m2MgbMaw4bujbmcgdOG7qyBz4buxIGzhuqV5IG3huqt1IG5n4bqrdSBuaGnDqm4gbOG6t3AgbOG6oWkgdHJvbmcgcGjDoWkgRnJlcXVlbnRpc3QuDQoNClRhIGPDsyB0aOG7gyB0w7l5IGNo4buNbiBuZ8aw4buhbmcgY2hvIEhESTogdGjDrSBk4bulIDkwJSBoYXkgOTUlDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KaGRpKG1vZCxwcm9iPWMoMC45MCwgMC45NSwgMC45NzUpLHR5cGU9ImZpeGVkIiklPiVwcmludChkaWdpdHM9MykNCmBgYA0KDQpDaG8gbcO0IGjDrG5oIGjhu5NpIHF1eSBsb2dpc3RpYywgdGEgY8OzIHRo4buDIMOhcCBk4bulbmcgbeG7mXQgaMOgbSBob8OhbiBjaHV54buDbiBleHAoICnEkeG7gyBjw7MgSERJIGNobyBPZGRzLXJhdGlvOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmhkaShsb2dtb2QscHJvYj1jKDAuOCwgMC45LCAwLjk1KSx0eXBlPSJmaXhlZCIsdHJhbnM9ImV4cCIpJT4lcHJpbnQoZGlnaXRzPTMpDQoNCmBgYA0KDQpOaOG6rW4geMOpdDogaMOgbSBoZGkoKSBy4bqldCB0aeG7h24gZOG7pW5nIGNobyBt4bulYyB0acOqdSBraOG6o28gc8OhdCBuaGFuaCDDvSBuZ2jEqWEgdGjhu5FuZyBrw6ogY+G7p2EgcGjDom4gcGjhu5FpIGjhuq11IG5naGnhu4dtLiBTbyB24bubaSBuaOG7r25nIGjDoG0gdMOzbSB04bqvdCBjaHXhu5dpIE1DTUMgY+G7p2EgYnJtcywgYnJvb20gaGF5IGNvZGEsIGjDoG0gaGRpIGxpbmggaG/huqF0IGjGoW4ga2hpIGNobyBwaMOpcCBuZ8aw4budaSBkw7luZyDhuqVuIMSR4buLbmggbmhp4buBdSBuZ8aw4buhbmcgcHJvYmFiaWxpdHk7IHR1eSBuaGnDqm4gbsOzIGtow7RuZyBjdW5nIGPhuqVwIGdpw6EgdHLhu4sgdHJ1bmcgdMOibSAodHJ1bmcgYsOsbmgsIHRydW5nIHbhu4spIGPhu6dhIHBow6JuIGLhu5EgbsOgeS4NCg0KIyBIw6BtIHJvcGUoKToga2hv4bqjbmcgdsO0IGhp4buHdSB0aOG7sWMgZOG7pW5nDQoNCkjDoG0gcm9wZSgpIGNobyBwaMOpcCDDoXAgZOG7pW5nIG3hu5l0IGtob+G6o25nIHTDuXkgY2jhu41uIFstYixiXSB24bubaSAtYiB2w6AgYiBsw6AgaGFpIG5nxrDhu6FuZyB0csOqbiB2w6AgZMaw4bubaSwgduG7m2kgZ2nhuqMgxJHhu4tuaCBsw6AgcGjhuqduIHBow6JuIHBo4buRaSBo4bqtdSBuZ2hp4buHbSByxqFpIHbDoG8gYsOqbiB0cm9uZyBraG/huqNuZyBuw6B5IHPhur0gxJHGsOG7o2MgeGVtIG5oxrAgdsO0IGhp4buHdSAoaGF5IGtow7RuZyBjw7Mgw70gbmdoxKlhKSB24buBIG3hurd0IHRo4buxYyBk4bulbmcuIFRow60gZOG7pSBu4bq/dSBPZGRzLXJhdGlvIG7hurFtIHRyb25nIGtob+G6o25nIFstMS41LDEuNSBdY2hvIGJp4bq/dCB0YSBjaOG7iSBjaOG6pXAgbmjhuq1uIE9SIGPDsyDDvSBuZ2jEqWEgbuG6v3UgZ2nDoSB0cuG7iyBj4bunYSBuw7MgPiAxLjUgaGF5IDwtMS41Lg0KDQpUaMOtIGThu6UgY2hvIG3DtCBow6xuaCBNQU5DT1ZBLCB0YSDEkeG6t3Qgcm9wZSA9IC0yIMSR4bq/biAxLCBr4bq/dCBxdeG6oyBuaMawIHNhdQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0Kcm9wZShtb2Qscm9wZT1jKC0yLDEpLHR5cGU9ImZpeGVkIiklPiVwcmludChkaWdpdHM9MykNCg0KYGBgDQoNCkvhur90IHF14bqjIG7DoHkgxJHGsOG7o2MgZGnhu4VuIGdp4bqjaSBsw6A6ICBIb+G6oXQgY2jhuqV0IE1ETCBsw6BtIGdp4bqjbSBodXnhur90IMOhcCB24bubaSBoxqFuIDg2ICUgcGjDom4gcGjhu5FpIGjhuq11IG5naGnhu4dtIG7hurFtIG5nb8OgaSBuZ8aw4buhbmcgLTINCg0KTmjhuq1uIHjDqXQ6IFNvIHbhu5tpIGNvZGUgdGjhu6cgY8O0bmcsIGjDoG0gcm9wZSBj4bunYSBwYWNrYWdlIHNqc3RhdHMga2jDtG5nIHjDoWMgxJHhu4tuaCDEkcaw4bujYyB04buJIGzhu4cgbeG6rXQgxJHhu5kgcGjDom4gYuG7kSBu4bqxbSB0csOqbiB2w6AgZMaw4bubaSBraG/huqNuZyByb3BlLCBuaMawbmcgY2jhu4kgbeG7m2kgdMOtbmggxJHGsOG7o2MgYmFvIG5oacOqdSAlIG7hurFtIHRyb25nIGhheSBuZ2/DoGkgUk9QRS4NCg0KVMawxqFuZyB04buxLCB0YSDDoXAgZOG7pW5nIFJPUEU9LTIgxJHhur9uIDIgY2hvIE9SIGPhu6dhIG3DtCBow6xuaCBsb2dpc3RpYyBuaMawIHNhdToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpyb3BlKGxvZ21vZCxyb3BlPWMoLTIsMiksdHJhbnM9ImV4cCIpJT4lcHJpbnQoZGlnaXRzPTMpDQoNCmBgYA0KDQpUaGVvIGvhur90IHF14bqjIG7DoHksIGNo4buJIGPDsyB54bq/dSB04buRIELhu4duaCBsw70gVGjhuq1uIG3huqFuIHTDrW5oIGzDoCBjw7MgT1IgxJHDoW5nIGvhu4MgbmjhuqV0ICg4MCUgbuG6sW0gbmdvw6BpIG5nxrDhu6FuZyAtMjoyKS4NCg0KIyBIw6BtIEVxdWl2YWxlbmNlIHRlc3Q6IGtp4buDbSDEkeG7i25oIGdp4bqjIHRodXnhur90IHbDtCBoaeG7h3UNCg0KSMOgbSBFcXVpdmFsZW5jZSB0ZXN0IHRo4buxYyBoaeG7h24gbeG7mXQga2nhu4NtIMSR4buLbmggxJHhu4Mgc3V5IGRp4buFbiB0aOG7kW5nIGvDqiB04buxIMSR4buZbmcgY2hvIG3hu5dpIGhp4buHdSDhu6luZyB0cm9uZyBtw7QgaMOsbmggQmF5ZXMsIGThu7FhIHbDoG8gMSBraG/huqNuZyB2w7QgaGnhu4d1IHRo4buxYyBk4bulbmcgY2h1bmcuIEdp4bqjIHRodXnhur90IGPhuqduIGtp4buDbSDEkeG7i25oIGzDoCBsaeG7h3UgcGjDom4gYuG7kSBo4bqtdSBuZ2hp4buHbSBjw7MgbuG6sW0gbmdvw6BpIFJPUEUgaGF5IGtow7RuZyA/IEvhur90IHF14bqjIGPDsyB0aOG7gyBsw6A6IENo4bqlcCBuaOG6rW4gZ2nhuqMgdGh1eeG6v3QsIGLDoWMgYuG7jyBnaeG6oyB0aHV54bq/dCBob+G6t2MgSG/DoGkgbmdoaSwga2jDtG5nIHRo4buDIHF1eeG6v3QgxJHhu4tuaC4gDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZXF1aV90ZXN0KG1vZCxvdXQ9InBsb3QiLHJvcGU9YygtMiwtMSksZWZmX3NpemUgPSAwLjEpDQoNCmVxdWlfdGVzdChtb2Qsb3V0PSJ0eHQiLHJvcGU9YygtMiwtMSksZWZmX3NpemUgPSAwLjEpDQoNCmBgYA0KDQpUxrDGoW5nIHThu7EsIHRhIGPDsyB0aOG7gyBzdXkgZGnhu4VuIHRo4buRbmcga8OqIGNobyBPUiBj4bunYSBtw7QgaMOsbmggbG9naXN0aWMNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQplcXVpX3Rlc3QobG9nbW9kLG91dD0icGxvdCIscm9wZT1jKC0xLDEpLGVmZl9zaXplID0gMC4xLHRyYW5zPSJleHAiKQ0KDQplcXVpX3Rlc3QobG9nbW9kLG91dD0idHh0Iixyb3BlPWMoLTEsMSksZWZmX3NpemUgPSAwLjEsdHJhbnM9ImV4cCIpDQpgYGANCg0KTmjhuq1uIHjDqXQ6IMSQw6J5IGzDoCBt4buZdCDDvSB0xrDhu59uZyB0aMO6IHbhu4ssIHbDrCBr4bq/dCBxdeG6oyBj4bunYSBraeG7g20gxJHhu4tuaCBjw7MgdGjhu4MgZ2nDunAgxJHGsGEgcmEgcXV54bq/dCDEkeG7i25oIHbhu4EgIsO9IG5naMSpYSB0aOG7kW5nIGvDqiIgY+G7p2EgaGnhu4d1IOG7qW5nIHRyb25nIG3DtCBow6xuaCwgZOG7sWEgdsOgbyBST1BFLiBUdXkgbmhpw6puLCBuaMaw4bujYyDEkWnhu4NtIGPhu6dhIGPDoWNoIGzDoG0gbsOgeSDEkcOzIGzDoCBjaOG7iSBjw7MgMSBuZ8aw4buhbmcgdsO0IGhp4buHdSBjaHVuZyBjaG8gbeG7jWkgaGnhu4d1IOG7qW5nLiBQaMawxqFuZyBwaMOhcCBzdXkgZGnhu4VuIGLhurFuZyBCYXllcyBmYWN0b3IgbGluaCBob+G6oXQgaMahbiBuaGnhu4F1LiBUdXkgYmnhu4N1IMSR4buTIHBow6JuIGLhu5EgaOG6rXUgbmdoaeG7h20gxJHGsOG7o2MgY3VuZyBj4bqlcCwgbsOzIGPDsyBwaOG6qW0gY2jhuqV0IGtow7RuZyB04buRdCBi4bqxbmcgY8OhYyBiaeG7g3UgxJHhu5MgxJHGsOG7o2MgduG6vSB0aOG7pyBjw7RuZyB04burIGdncGxvdDIgdsOgIGThu68gbGnhu4d1IE1DTUMgdGjDtC4NCg0KIyBIw6BtIHRpZHlfc3RhbjogVMOzbSB04bqvdCBtw7QgaMOsbmgNCg0KSMOgbSB0aWR5X3N0YW4gY2hvIHBow6lwIHTDs20gdOG6r3QgZOG7hSBkw6BuZyBu4buZaSBkdW5nIG3DtCBow6xuaCAsIGLhuqNuIHRow6JuIG7DsyBiYW8gZ+G7k20gY+G6oyBjw7RuZyBk4bulbmcgY+G7p2EgaMOgbSBoZGkgbmjGsG5nIGLhu5Ugc3VuZyB0aMOqbSBnacOhIHRy4buLIFRydW5nIHbhu4sgY+G7p2EgcGjDom4gYuG7kSBo4bqtdSBuZ2hp4buHbSwgdsOgIDMgdHLhu4sgc+G7kSBraeG7g20gdHJhIHBo4bqpbSBjaOG6pXQgY2h14buXaSBNQ01DOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnRpZHlfc3Rhbihtb2QscHJvYj0wLjk1KQ0KYGBgDQoNCk5nb8OgaSBoaeG7h3Ug4bupbmcgY2jDrW5oIHRhIGPDsm4gY8OzIHRo4buDIHTDs20gdOG6r3QgcmFuZG9tIGVmZmVjdHMNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQp0aWR5X3N0YW4obW9kLHByb2I9MC45NSx0eXBlPSJyYW5kb20iKQ0KDQpgYGANCg0KVuG7m2kgbcO0IGjDrG5oIGxvZ2lzdGljLCB0YSBjw7MgdGjhu4Mgw6FwIGThu6VuZyBow6BtIGV4cCgpIMSR4buDIHTDrW5oIE9SIHRy4buxYyB0aeG6v3AgdsOgIHTDs20gdOG6r3Qga+G6v3QgcXXhuqMgbsOgeToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQp0aWR5X3N0YW4obG9nbW9kLHByb2I9MC45NSx0cmFucz0iZXhwIikNCmBgYA0KDQpOaOG6rW4geMOpdDogSMOgbSB0aWR5X3N0YW4gY8OzIG5oaeG7gXUgxrB1IMSRaeG7g20sIG5oxrAgZOG7hSBz4butIGThu6VuZywgY2jhu4kgZ2nhu68gbOG6oWkgbmjhu69uZyBr4bq/dCBxdeG6oyBjaMOtbmggeeG6v3UgbmjhuqV0IGPhu6dhIG7hu5lpIGR1bmcgbcO0IGjDrG5oLCBraOG6oyBuxINuZyBob8OhbiBjaHV54buDbiBk4buvIGxp4buHdSB0cuG7sWMgdGnhur9wIGNobyBwaMOpcCB0w61uaCBJbmNpZGVuY2UgcmF0ZSByYXRpbyAoSVJSKSBjaG8gY291bnQgZGF0YSBoYXkgT2Rkcy1yYXRpbyBjaG8gbG9naXN0aWMgbW9kZWwuIA0KDQpIw6BtIHRpZHlNQ01DIGPhu6dhIHBhY2thZ2UgYnJvb20gY8WpbmcgY8OzIGto4bqjIG7Eg25nIHTDs20gdOG6r3QgbuG7mWkgZHVuZyBtw7QgaMOsbmggQmF5ZXMgbmjGsG5nIGPDuiBwaMOhcCBj4bunYSBuw7MgcGjhu6ljIHThuqFwIGjGoW4gbmhp4buBdQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmJyb29tOjp0aWR5TUNNQyhtb2QsY29uZi5pbnQgPSBUUlVFLCANCiAgICAgICAgICAgICAgICBlc3RpbWF0ZS5tZXRob2QgPSAibWVkaWFuIiwgDQogICAgICAgICAgICAgICAgY29uZi5tZXRob2QgPSAiSFBEaW50ZXJ2YWwiLA0KICAgICAgICAgICAgICAgIHBhcnM9YygiYl9JbnRlcmNlcHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAiYl9UcmVhdG1lbnRNREwiLA0KICAgICAgICAgICAgICAgICAgICAgICAiYl9Eb3NlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgImJfVHJlYXRtZW50TURMOkRvc2UiLA0KICAgICAgICAgICAgICAgICAgICAgICAic2lnbWEiKSkNCmBgYA0KDQojIEjDoG0gcGxvdF9tb2RlbDoga2jhuqNvIHPDoXQgdHLhu7FjIHF1YW4gDQoNCk5nb8OgaSBraOG6oyBuxINuZyB0w7NtIHThuq90IG7hu5lpIGR1bmcgbcO0IGjDrG5oLCBwYWNrYWdlIHNqUGxvdCBjw7JuIGPDsyBt4buZdCBow6BtIHLhuqV0IHRp4buHbiBk4bulbmcga2jDoWMgbMOgIHBsb3RfbW9kZWwsIGjDoG0gbsOgeSBjaG8gcGjDqXAgbcO0IHThuqMgdHLhu7FjIHF1YW4gdOG6pXQgY+G6oyBjw6FjIGxv4bqhaSBtw7QgaMOsbmggdGjDtG5nIGThu6VuZyB0cm9uZyBSLCBiYW8gZ+G7k20gbcO0IGjDrG5oIFNUQU4gdsOgIGJybXMuIE7DsyBz4bq9IHbhur0gbeG7mXQgYmnhu4N1IMSR4buTIGJveHBsb3QgbcO0IHThuqMgcGjDom4gYuG7kSBo4bqtdSBuZ2hp4buHbSBjaG8gbeG7l2kgaGnhu4d1IOG7qW5nIHRyb25nIG3DtCBow6xuaDoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpwbG90X21vZGVsKG1vZCwNCiAgICAgICAgICAgdmxpbmUuY29sb3IgPSAicmVkIiwNCiAgICAgICAgICAgc29ydC5lc3QgPSBUUlVFLA0KICAgICAgICAgICBzaG93LnZhbHVlcyA9IFRSVUUsIA0KICAgICAgICAgICB2YWx1ZS5vZmZzZXQgPSAuMykrdGhlbWVfYncoKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdF9tb2RlbChtb2QsdHlwZT0icmUiLA0KICAgICAgICAgICB2bGluZS5jb2xvciA9ICJyZWQiLA0KICAgICAgICAgICBzb3J0LmVzdCA9IFRSVUUsDQogICAgICAgICAgIHNob3cudmFsdWVzID0gVFJVRSwgDQogICAgICAgICAgIHZhbHVlLm9mZnNldCA9IC4zKSt0aGVtZV9idygpDQpgYGANCg0KS2hpIMOhcCBk4bulbmcgY2hvIG3DtCBow6xuaCBsb2dpc3RpYywgdGEgY2jhu4kgY+G6p24gdGjDqm0gaMOgbSBob8OhbiBjaHV54buDbiBleHAgdHJvbmcgdMO5eSBjaOG7iW5oIHRyYW5zIMSR4buDIHbhur0gYmnhu4N1IMSR4buTIGNobyBPUjoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpwbG90X21vZGVsKGxvZ21vZCx0cmFucz0iZXhwIiwNCiAgICAgICAgICAgdmxpbmUuY29sb3IgPSAicmVkIiwNCiAgICAgICAgICAgc29ydC5lc3QgPSBUUlVFLA0KICAgICAgICAgICBzaG93LnZhbHVlcyA9IFRSVUUsIA0KICAgICAgICAgICB2YWx1ZS5vZmZzZXQgPSAuMywNCiAgICAgICAgICAgc2hvdy5wPVRSVUUpK3RoZW1lX2J3KCkNCmBgYA0KDQpOaOG6rW4geMOpdDogQmnhu4N1IMSR4buTIHRy4buxYyBxdWFuIGNobyBtw7QgaMOsbmggQmF5ZXMgc+G7rSBk4bulbmcgQm94cGxvdCB0aGF5IHbDrCBwb2ludCByYW5nZSBwbG90IHbDrCB0YSDEkWFuZyBraOG6o28gc8OhdCAxIHBow6JuIGLhu5EgbGnDqm4gdOG7pWMgdGjhu7FjIHPhu7EgY+G7p2EgbeG7l2kgdGhhbSBz4buRIHRyb25nIG3DtCBow6xuaC4gQ8OhYyBiaeG7g3UgxJHhu5MgbsOgeSBnaeG6o24gZOG7iyB2w6AgduG7q2EgxJHhu6cgY2hvIHZp4buHYyBjw7RuZyBi4buRIGvhur90IHF14bqjLiBUdXkgbmhpw6puLCBwaOG6qW0gY2jhuqV0IG3hu7kgdGh14bqtdCBj4bunYSBjaMO6bmcga2jDtG5nIHRo4buDIHNvIHPDoW5oIMSRxrDhu6NjIHbhu5tpIGPDoWMgYmnhu4N1IMSR4buTIMSRxrDhu6NjIGThu7FuZyB0aOG7pyBjw7RuZywgbmjhuqV0IGzDoCBkZW5zaXR5IHBsb3QgY+G7p2EgcGFja2FnZSBnZ3JpZGdlcy4NCg0KDQojIEvhur90IGx14bqtbg0KDQpWw6BvIG7Eg20gMjAxNiBjw7MgbmfGsOG7nWkga2jhurNuZyDEkeG7i25oIGzhuqFjIHF1YW4gcuG6sW5nICJT4bubbSBtdeG7mW4gZ8OsIHThuqV0IGPhuqMgbmfGsOG7nWkgZMO5bmcgUiDEkeG7gXUgc+G6vSB0aGVvIHRyxrDhu51uZyBwaMOhaSBCYXllcyIuIMSQaeG7gXUgbsOgeSBjw7MgdGjhu4MgdGluIMSRxrDhu6NjIG7hur91IHRhIGJp4bq/dCBy4bqxbmcgaGnhu4duIG5heSBwaMawxqFuZyBwaMOhcCBzdXkgZGnhu4VuIEJheWVzIMSRw6MgdHLhu58gbsOqbiBk4buFIGTDoG5nIHbDoCBwaOG7lSBiaeG6v24gaMahbiBiYW8gZ2nhu50gaOG6v3QgdHJvbmcgUi4gTmfDtG4gbmfhu68gU1RBTiB0cuG7nyB0aMOgbmggbuG7gW4gdOG6o25nIGNodW5nIGNobyB0aOG7kW5nIGvDqiBCYXllcywgw610IG5o4bqldCAzIHBhY2thZ2UgdHJvbmcgUiBjaG8gcGjDqXAga+G6v3QgbuG7kWkgduG7m2kgU1RBTiwgdHJvbmcgxJHDsyBicm1zIGzDoCBjw7RuZyBj4bulIMSRxqFuIGdp4bqjbiB2w6AgbeG6oW5oIG5o4bqldC4gTmjhu69uZyBwYWNrYWdlcyBuaMawIHNqc3RhdHMgdsOgIHNqUGxvdCDEkcOjIGN1bmcgY+G6pXAgY2hvIG5nxrDhu51pIGTDuW5nIG3huqNuaCBnaMOpcCBjdeG7kWkgY8O5bmcgY8OybiB0aGnhur91IC0ga2hhaSB0aMOhYyBtw7QgaMOsbmggQmF5ZXMuIFbhu5tpIG5o4buvbmcgaMOgbSDEkcaw4bujYyBnaeG7m2kgdGhp4buHdSB0cm9uZyBiw6BpLCB0YSBob8OgbiB0b8OgbiBjw7MgdGjhu4MgaHkgduG7jW5nIHbhu4EgbeG7mXQgdMawxqFuZyBsYWkgcuG6pXQgcuG7mW5nIG3hu58gY2hvIHRyxrDhu51uZyBwaMOhaSBCYXllcywgdOG7q25nIGLGsOG7m2MgdGhheSB0aOG6vyBjaG8gbmjhu69uZyBwaMawxqFuZyBwaMOhcCBraeG7g20gxJHhu4tuaCBj4buVIMSRaeG7g24ga2jDoWMu